* Let API callers supply a token for gr.OAuthToken endpoints
`gr.OAuthToken` was only ever populated from the OAuth session cookie, so it
was always `None` for an API caller — no browser, no session. Any app whose
function takes one could not be driven programmatically at all, which is how
this surfaced: a deployed `gr.Workflow` reached its model nodes with no token
and failed with "Sign in with your HF account to use this model".
Callers can now pass `Client(..., oauth_token=...)`, which travels in the
request body as a reserved `oauth_token` field rather than a header — a Space
sits behind a proxy that strips `x-hf-*`, so a header never arrives. Because it
rides beside `data` instead of in it, it never becomes a positional argument or
appears in an endpoint's parameter schema, and it can't be captured by flagging
or cached examples. It also works from `curl` and any other client, with no
client-side support needed.
A token is sent only to endpoints that declare they take one: `get_api_info`
reports `oauth_token: "required" | "optional"` per endpoint, derived from the
function signature, and the client consults that before including the field. So
an app cannot collect tokens from calls that had no reason to carry one, and
`view_api()` states plainly which endpoints act on the caller's behalf.
`oauth_token=` is deliberately separate from `token=`, which only authenticates
the caller to the app, and is never inferred from a locally saved token.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Support oauth_token in the JS client, and move the e2e test
The JS client gains the same `oauth_token` option and the same gating: it is
included in the payload only for endpoints whose api_info declares they take a
gr.OAuthToken, so parity with the Python client holds and an app still cannot
collect tokens from calls that had no reason to carry one.
The end-to-end check moves into test_external.py, which already hits real
Spaces and is marked flaky and serial, rather than living in a file of its own.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Address review: narrow the token flag, and close two ways to bypass the gate
`oauth_token_requirement` scanned every annotation, including the return type,
so a function that *returned* an OAuthToken was reported as one that receives
one. It now looks only at parameters.
`/call/v2` popped `oauth_token` from every request body, which reserved the name
globally and would swallow a real parameter that happened to be called that. It
is only treated as reserved for endpoints that declare they take a token.
In the client, `**kwargs` expanded after the gated payload, so passing
`oauth_token=` as an ordinary keyword argument would have sent a token to an
endpoint that never asked for one. The computed payload now goes last and wins.
Also reworded a docstring that opened with a quoted word, which `ruff format`
had to space away from the opening triple quote.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Satisfy the backend type checker
`oauth_token_requirement` returned plain `str`, which cannot be assigned to the
`Literal["required", "optional"]` key on the APIEndpointInfo TypedDict, and the
e2e test subscripted `view_api()` (overloaded on return_format) and iterated
client.endpoints without narrowing the value type.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Surface oauth_token in the API docs, and fix the JS client gate
The per-endpoint `oauth_token` requirement reached `/info` but stopped there,
so nothing downstream showed it or acted on it:
- `transform_api_info()` rebuilt each endpoint as {parameters, returns, type},
dropping `oauth_token`. `submit()`'s gate therefore never matched and the JS
client never sent a token at all.
- The view-API page said nothing about which endpoints act on the caller's
behalf, and its snippets omitted `oauth_token` so copy-paste didn't work.
- Sending `oauth_token` to an endpoint that takes no token 500'd on
`construct_args`; it is now stripped from the args either way and only
honored where the fn declares one.
Also trims the explanatory inline comments added across the branch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Make the oauth_token notice font sizes consistent
The first paragraph inherited --text-md while .desc set --text-lg, so the
subdued explanation rendered larger than the primary statement. Both are prose
now at --text-lg, and the inline mono spans drop a step to sit optically level
with it — matching the sibling parameters section (h4 14px, prose 16px, mono
14px).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Stop reserving the oauth_token body key on every endpoint
Popping it unconditionally fixed the 500 on endpoints that take no token, but
it also swallowed the value of any endpoint whose own parameter is named
oauth_token (github-pilot caught this). The name is now reserved only where the
fn declares a gr.OAuthToken; elsewhere it stays an ordinary argument if the
endpoint has one by that name, and is dropped if it does not — so neither the
500 nor the swallowing happens.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Fix @gradio/client packaging: real CDN bundle at dist/index.min.js and CJS require entry
- Add a minified, self-contained browser ES module bundle at dist/index.min.js,
the path documented for jsDelivr CDN usage (#10028)
- Add a CommonJS bundle at dist/index.cjs and a 'require' condition to the
exports map so require('@gradio/client') works on all supported Node
versions (#9214 symptom)
- Add 'types' condition / top-level types field
- Update README CDN snippet to the working <script type="module"> import form
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Use fix: prefix in changeset
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Address review: CDN build implies browser build; verify dist artifacts after build
- CDN_BUILD=true now forces the browser build path so es+cjs can never
collide on the same output filename
- Add scripts/verify_dist.mjs, run at the end of 'pnpm build', which fails
the build if any dist artifact referenced by package.json (exports map,
main/module/types) was not emitted
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Preserve File names in JS client uploads and use handle_file in generated JS API snippets
- handle_file() no longer strips a File down to a bare Blob, and
walk_and_store_blobs() no longer re-wraps Blobs/Files, so original
filenames and MIME types survive the multipart upload (fixes#10758).
- The auto-generated JavaScript API snippet now imports handle_file and
wraps fetched blobs with handle_file() for file-based components
(fixes the snippet half of #12077).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Guard Buffer instanceof check in handle_file for browser environments
Addresses Copilot review: in browsers without a Buffer polyfill,
`file_or_url instanceof Buffer` threw a ReferenceError before the Blob
branch was reached. Use the same globalThis.Buffer guard as
walk_and_store_blobs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Remove snippet unit tests
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Fix JS client private-Space auth and error handling (hf_token alias, no more raw 'map' TypeErrors / unhandled rejections)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Address Copilot review: trim space_id, slash-tolerant named endpoint lookup, queue flag from skip_queue
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: trigger CI
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Fix Windows filename and path handling across uploads, node lookup, and preview
Prefix reserved DOS device names in strip_invalid_filename_characters, silence where/which node stderr, and use proper file:// URLs for Windows paths.
Co-authored-by: Cursor <cursoragent@cursor.com>
* add changeset
* Keep Model3D unhandled rejection suppressor active for full test run
Babylon.js can reject after the file-level afterAll removed the listener, causing flaky js-test failures.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Check first dot segment for Windows reserved device names
Windows resolves device names from the segment before the first dot
(NUL.tar.gz is the NUL device), but the check used os.path.splitext,
which splits on the last dot, letting names like CON.tar.gz through.
Match CPython's ntpath.isreserved instead, and extend the reserved set
with CONIN$/CONOUT$ and the superscript COM/LPT variants.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Fix in-flight events breaking when an app is hot-reloaded
When running `gradio app.py`, saving the file while a function (especially
a generator) is mid-run caused every subsequent yield to fail with
"Returned component ... not specified as output of function" or a KeyError
in the state holder, because the event kept processing against a mix of
old and new blocks config.
- Remap pending/running queue events to the new app's BlockFunction
(matched by api_name) when blocks are swapped after a reload
- Re-read the event's fn on each generator iteration in the queue loop
- Fall back to matching returned dict components to outputs by key when
the component object is stale
- Carry over pending streaming/diff state so in-flight generators keep
sending diffs instead of resetting the stream protocol
Fixes#8712
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Fix type check failure in reload Event test
Pass a real fastapi.Request instead of None so ty accepts Event construction.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Handle output components added during a hot reload
When a generator is mid-run and the app is hot-reloaded to add a new output
component, the reassigned function emits an update for a component that is not
yet in the session config, and the diff stream gains an extra output.
- Guard the state lookup in postprocess_data so an update for a freshly-added
output falls back to the block's own constructor args instead of KeyError
- Add SessionState.get for the above
- Grow the Python client's diff buffer when a new output appears mid-stream
- Start unseen outputs from null in the JS client's diff stream
Relates to #8712
Co-authored-by: Cursor <cursoragent@cursor.com>
* changes
* Address Copilot review: snapshot queue collections during reload, catch settled() rejection
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: cover in-flight generator streaming across hot reload
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix Prettier formatting in state.svelte.ts
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Fix hot reload rebinding across repeated reloads
* Stabilize chatbot feedback browser test
* Preserve loading status when dependency ids shift on reload
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix frontend proxy asset urls
* Fix SSR login flash and tighten proxy-origin URL rewriting
- Client.connect no longer rejects when fetching /info fails, which was
making the SSR server render a spurious login page ("Could not get API
info") before hydration recovered.
- resolve_current_origin_url only adopts the page origin when the page is
actually served by the gradio server (window.gradio_config present or an
explicit current_location), so same-host embeds and the vite dev server
keep the backend's own origin.
- Align the iframeResizer loader in index.html with the shared helper
(hostname-only match) so it also works behind protocol-changing proxies.
- Revert the App.configure_app root_path merge: preserving FastAPI's
constructor root_path made mounted apps with app_kwargs root_path 404.
- Drop the js/upload re-export indirection and its duplicated test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Run window-fallback URL tests only in browser mode
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Use msw runtime overrides instead of replacing initial handlers in client test
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ci: choose installable gradio release for frontend profiling
* test: avoid raw github downloads in component fixtures
* Address review: remove changesets, simplify base install to plain pip install, drop client test and conftest github mock
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* add changeset
* Fix remaining proxy request and root path regressions
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
* Fire state.change for streaming events
State updates from a .stream() event (e.g. gr.Audio(streaming=True))
did not trigger the state's .change() listener, although the same update
from a regular event did.
The backend already sends changed_state_ids in the process_streaming
message, but the frontend dropped it in two places:
- handle_message did not copy changed_state_ids into the status for the
process_streaming case (process_generating and process_completed
already do).
- the dispatch loop only called dispatch_state_change_events for the
complete and generating stages, so streaming chunks (stage
"streaming") were skipped.
Carry changed_state_ids through the process_streaming case and handle
the streaming stage alongside generating. The change still fires only
when the state value actually changes, since the backend gates
changed_state_ids behind a deep-hash comparison.
Closes#10285
* add changeset
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
* Use same-origin credentials in JS client
Cross-origin embeds (<gradio-app>) broke after the Spaces reverse
proxy stopped setting Access-Control-Allow-Credentials on cross-origin
responses: every client request used credentials: "include", so the
config fetch and queue/join were blocked by CORS.
Switch all client requests to credentials: "same-origin" so embed
requests go out uncredentialed and pass CORS, while same-origin app
use still sends cookies (auth preserved) and the Authorization token
path is unaffected. Also drop the Content-Type header from the
body-less /config and api info GETs so they stay CORS-simple and
avoid a preflight.
Closes#13554
* add changeset
* Add credentials option to the JS client
Address review feedback on the same-origin switch: it silently breaks
deployments that call a cookie-authenticated Gradio app from another
origin (e.g. SSO session cookies with credentialed CORS configured).
Add ClientOptions.credentials so such deployments can opt back in via
Client.connect(url, { credentials: "include" }). All requests default
to same-origin, keeping the cross-origin embed fix. The login POST in
get_cookie_header takes the mode as an optional parameter since it is
not client-bound.
* Trim overlapping credentials tests
Per review, the per-call-site request-shape tests repeated the same
two behaviors. Keep one test for the same-origin default and missing
Content-Type (the /config path) and one for the credentials opt-in;
drop the duplicates in view_api and post_data.
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
* fix(client): make predict() reject on unknown endpoint so errors are catchable
predict() ran its body inside `new Promise(async (resolve, reject) => ...)`.
submit() throws synchronously via get_endpoint_info when the endpoint name /
fn_index doesn't exist, but because the executor is an async function with no
try/catch, that throw rejected the executor's own (discarded) promise instead
of the promise predict() returns. The returned promise therefore never settled,
so neither `.catch()` nor `try/catch` could handle the error and it surfaced as
an unhandledRejection.
Wrap the executor body in try/catch and reject() on error.
Fixes#12101
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(client): settle predict after final data
* test(client): remove mocked predict ordering case
* refactor(client): remove predict async promise wrapper
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix format diagnostics
* add changeset
* Fix format diagnostics
* Fix frontend runtime test regressions
* add changeset
* Fix post-merge frontend diagnostics
* add changeset
* Simplify optional spaces imports
* add changeset
* Enable frontend lint and typecheck in CI
* add changeset
* Address review comments after merging main
- Resolve merge conflicts in FullscreenButton wiring (keep dispatch where main added it; keep null guard + svg renderer in nativeplot)
- gallery: make handle_save take a root-bound upload callback; drop unused Client import
- tootils: drop duplicate ILoadingStatus, use the @gradio/statustracker type
- types: widen frontend_fn return type to match process_frontend_fn runtime
- dataframe: remove leftover console.log in measure_row
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Allow I18nData in choice display names for ty typecheck
PR #13534 added runtime support for i18n'd choice display names in
Radio/CheckboxGroup/Dropdown/SimpleDropdown but didn't widen the
`choices` type, so `ty` flags `demo/i18n/run.py`. Widen the tuple
display side to `str | I18nData` to match `label`/`info` and the
runtime behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* changes
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Close iterator on terminal error in JS client
The submit() async iterator fired an "error" status on terminal
errors (queue full, validation, broken connection, server-side
exception mid-stream) but never called close(). The done flag stayed
false, so a for-await consumer hung forever waiting on the next event.
Call close() on the error terminals, mirroring the success path. Add a
test that a server-side error mid-stream terminates the iterator.
* add changeset
* Make mid-stream error test actually stream before erroring
The test was named for a mid-stream server error but only fired a
terminal process_completed without any prior streamed output. Emit a
process_generating data event first so the error genuinely arrives
mid-stream, and assert the partial result is delivered before the
terminal error.
* Match real SSE payload shape in error test
handle_message() reads title/visible/duration from the process_completed
output object, so put them inside output (with title at top level they
were silently ignored). Makes the simulated event representative of a
real server error payload.
* Update client/js/src/utils/submit.ts
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
* fix snippet generator crash on datetime values in Dataframe inputs
The _stringify_py function in snippet.py calls json.dumps on prepared
values, but datetime objects (and other non-JSON-native types) fall
through _prepare unchanged, causing a TypeError.
Pass default=str to json.dumps so that datetime, date, Decimal, UUID,
and similar types are serialized via their __str__ method instead of
raising.
Fixes#13278
* add changeset
---------
Co-authored-by: Freddy Boulton <41651716+freddyaboulton@users.noreply.github.com>
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
* fix: preserve special characters in uploaded filenames
Change filename sanitization from allowlist to blocklist approach.
Previously only alphanumeric chars and `._-, ` were allowed, stripping
valid characters like parentheses, brackets, braces, exclamation marks,
and unicode punctuation. Now only truly dangerous characters are removed:
path separators (/ \), null bytes, control characters, and Windows-
forbidden characters (< > : " | ? *).
Fixes#11983
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add DEL char and shell-dangerous chars to forbidden set, move regex to module level
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* add changeset
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
* perf: use deque for SSE pending message queues in gradio_client
pending_messages lists are drained front-to-back via .pop(0) inside
the SSE streaming loop. Each .pop(0) is O(n); switching to
collections.deque with .popleft() gives O(1) front removal.
* add changeset
* add changeset
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>