Commit graph

6,171 commits

Author SHA1 Message Date
pre-commit-ci[bot]
e392343329 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-07-14 21:07:39 +00:00
oobabooga
2e21aed7bc Studio: reject binary web_search fetches instead of decoding them into replacement chars 2026-07-14 18:00:44 -03:00
Nilay
bb80602174
Fix S3 tab flashing on reload (#7106)
* persist vision/audio model flags to fix S3 tab flash on reload

* persist modelType and reset capability flags on model switch
2026-07-14 06:47:24 -07:00
Gaurav Dubey
eb31d1e5ca
Studio: fix the permanent GGUF "update available" on no-symlink caches (#7113)
* Studio: fix the permanent GGUF "update available" on no-symlink caches

Without the symlink privilege (the default on Windows with Developer Mode
OFF), hf_hub_download MOVES the downloaded blob into snapshots/ instead of
symlinking it out of blobs/, so blobs/ is left empty and scan_cache_dir
reports blob_path = the snapshot file itself. Path(blob_path).name is then
the GGUF FILENAME, not the file's etag.

_repo_gguf_blob_map recorded that filename as the file's local blob hash, so
_variant_update_available_from_requirement's `remote_sha256 in local_set`
test could never match and every cached GGUF reported "update available"
forever. Re-downloading could not clear it: the same file is rewritten, still
with no blob.

Only treat Path(blob_path).name as a hash when the file really lives in the
cache's blobs/ dir; otherwise record a size identity so the file still appears
in the map (dropping it would make the update check read it as absent and
report the same phantom update). The comparison falls back to the remote
ExpectedFile.size only when the cached file carries no blob hash, so the
blob-hash path is unchanged wherever HF does produce blobs.

A remote requant that keeps the byte size identical is not detected in that
layout; re-hashing multi-GB GGUFs on the inventory hot path is the only
stricter option.

Fixes #7060

* Studio: match GGUF update checks by manifest sha256, closing the equal-size requant blind spot (#7060)

On a no-symlink cache (Windows without Developer Mode) blobs/ is empty, so the update check falls back to comparing byte size. A Studio download records each file's sha256 in its manifest, so feed that into the local identity set: the check can then match by hash and detect an equal-size requant. The size fallback now applies only when no real hash is present, so a manifest hash that differs is still reported as a genuine update. Adds regression tests.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Revert the manifest-sha256 identity merge; keep the size-identity fallback (#7060)

The download manifest is written before the transfer with the expected remote hashes, so it records download intent, not verified on-disk content, and completion is checked by size only. Merging those hashes into the local identity set could clear the update badge for an interrupted equal-size update that left the old bytes on disk. The accompanying all-size-identity gate also suppressed the size fallback whenever an older revision contributed a real blob hash, which re-showed a false update on mixed hash and size caches. Restoring the plain size-identity fallback keeps the fix without those regressions.

* Studio: don't delete no-symlink GGUFs during stale-variant reclaim (#7060)

On a no-symlink cache (Windows without Developer Mode) the downloaded file is moved into snapshots/ and scan_cache_dir reports its blob_path as that snapshot file, whose name is the filename, not an etag. reclaim_replaced_gguf_variant treated that name as a blob hash, which never matches the current hashes to keep, so it unlinked the freshly downloaded file. Only extract a deletable hash when the blob path is a real cache blob under the repo blobs directory, and keep any file we cannot identify; stale no-symlink revisions leak rather than risk removing the current file. Adds a regression test.

* Studio: anchor GGUF blob-hash detection to the repo cache blobs dir (#7060)

The inventory update check and the stale-variant reclaim both decide whether a scanned file is a real cache blob (name is the etag) or a moved no-symlink snapshot file (name is the filename). Both now share one _is_real_cache_blob helper that anchors to the repo cache blobs directory instead of matching any parent folder named blobs, so a repo that ships GGUFs under its own blobs subdir is no longer misread as the cache blob store. Threads repo_path through _repo_gguf_blob_map. Adds a regression test.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Studio: tighten comments in the GGUF update-check helpers (#7060)

Post-review comment pass: shorten the internal blob-identity and size-fallback docstrings. Comments only, no behavior change.

---------

Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-14 06:45:46 -07:00
oobabooga
1e0d5ec6a9
Studio: pin llama.cpp update apply to the release the banner offered (#7112)
* Studio: pin llama.cpp update apply to the release the banner offered

* Document the pinned walk-back trade-off and cover win32

* Trim the pin comments

* Studio: verify a pinned llama.cpp update landed on the pinned release

The pin passes --published-release-tag so the installer resolves exactly the offered release. Also verify the result: if the post-install marker stays on the pinned repo but reports a different tag, the installer ignored the pin, so fail with a retryable error instead of a false success. A Vulkan/Intel host legitimately reroutes fork to upstream and drops the pin, so the check is scoped to the pinned repo.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: danielhanchen <michaelhan2050@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-14 06:45:16 -07:00
Michael Han
bc23135996
Unsloth: appearance palettes, customization options, and control restyle (#7077)
* Unsloth: appearance palettes, customization options, and control restyle

Adds Standard, Classic, and Minimal color palettes to Appearance settings,
each adapting to light and dark mode. Classic is a neutral enterprise look
that reserves its blue accent for toggles, badges, and focus rings; Minimal
is strictly black, grey, and white.

Adds customization options scoped to the active mode: accent, background,
and foreground colors with an in-app color picker, UI and code fonts with a
searchable dropdown covering bundled, device, and imported fonts, font file
import, UI and code font sizes, contrast, pointer cursors, reduce motion,
font smoothing, and translucent sidebar. Settings persist through the
personalization API with backend validation and sync across devices.

Restyles core controls for a cleaner, flatter look in both modes: bordered
white input fields, fully rounded pills for single-row controls, no drop
shadows, simple straight-line chevrons replacing all rounded arrow icons,
and consistent hover tones in dropdown menus. Popovers now portal into the
open dialog so their lists scroll correctly inside modal dialogs.

Moves Language into General settings and Chat defaults into the Chat tab
above the Canvas section.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Unsloth: appearance follow-ups, font options, and settings search

Neutralizes focus and selection rings across all palettes so highlighted
elements, including typing boxes and the selected palette card, never take
the accent color. The custom accent no longer recolors rings.

Restyles the color controls as filled pills showing the hex value inside,
with text and border contrast picked from the color's luminance. Menus in
popovers now match the app's dropdown menus: rounded-lg corners, tighter
padding, accent hover rows, and a bordered search field. Popovers inside
modal dialogs are modal so their lists scroll with the wheel. Outline
buttons share the same dark fills as dropdown triggers.

Adds heading and chat font options next to the UI and code fonts, each
using the searchable font dropdown and persisting through the
personalization API. Removes the translucent sidebar option end to end.

Adds settings search: a search field at the top of the settings sidebar
that filters setting names across every tab, grouped by tab with icons,
and jumps to the tab on click.

* Unsloth: use the shared accent token for dark hover fills

The settings dialog nav, its close button, the model selector, and the
project switcher hovered with hardcoded blue tinted greys (#3a3d43,
#2d2e32) in dark mode while every menu and sidebar uses --accent. All
hover and active pill fills now use the accent token so dark hovers are
the same everywhere and adapt to the active palette.

* Unsloth: settings search polish and jump to matched setting

Widens the settings dialog to 880px and the sidebar column to 248px so
the search field has more room. The search pill aligns with the left
start of the Settings title, gets more spacing above and below, and its
icon and placeholder sit slightly further left.

Search results now jump to the exact setting: rows and sections expose
their label as a data attribute, and picking a result opens the tab,
scrolls the matched row into view, and flashes it briefly.

* Unsloth: settings search bar spans the full nav pill width

The search field now starts and ends at the same edges as the nav hover
pills instead of being inset to the title text.

* Unsloth: address review findings on motion, sync, and font limits

Reduce motion Off now opts back out of the OS reduced-motion preference
for CSS animations via a force-motion class that the media rules skip,
and forcing reduce motion On keeps the loader exceptions (spinners,
loading dots, progress bars) animating.

When the color scheme follows the system, the resolved mode is now part
of the theme store snapshot, so an OS scheme flip re-renders consumers
and reapplies per-mode custom colors instead of leaving stale inline
variables from the previous mode.

Imported fonts get an aggregate size cap (4.4M characters) on both the
frontend sanitizer and the backend model so the persisted store always
fits browser localStorage quotas, with a clear error toast when an
import would exceed it. Backend validation also tightens imported font
names (rejects CSS delimiter characters) and requires strict base64
font data URLs, matching the frontend patterns.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Unsloth: profile toggle to hide the sloth in the chat greeting

Adds a Show greeting sloth switch to Settings > Profile. The chat welcome
hides the mascot when it is off. The preference persists locally and
through the personalization API, with backend validation and tests, and
the row is reachable from settings search in all four locales.

* Unsloth: control restyle, dropdown scrolling, and palette consistency

Settings sidebar puts search on top with the tab list under a small
Settings label. Combobox popups scroll with the wheel inside dialogs by
falling back to manual list scrolling while a dialog scroll lock is
active, and the local model selector popover became modal for the same
reason. Number inputs swap native spinners for a shared grey stepper
that clamps to min, max, and step. Run settings fields in light mode use
the same white fill and border as the settings dialog. Selection and
focus rings derive from each palette's border color instead of near
black, hover borders soften the same way, the Classic sidebar stays
white like Standard, decorative greens follow the palette accent, and
meaning-carrying marks like the hub verified badge keep the brand green
in every palette.

* Unsloth: palette card selection keyed off the palette attribute

Switching palettes restyles the whole page the moment data-palette lands
on the html element, but the React re-render that moves the selection
classes arrives later, so the ring and check briefly stayed on the
previous card with the new palette's colors. The active ring and check
now key off html[data-palette] in CSS, so they swap in the same style
pass that swaps the tokens. Also adds breathing room around the settings
search bar and under the Settings label, shortens the greeting sloth
description, and renames the avatar section to Or pick a sloth profile
picture in all locales.

* Unsloth: restore neutral rings, drop the palette check, sidebar spacing

Puts the ring tokens back to their fixed per palette values and removes
the hover border darkening, undoing the derived border experiment. The
selected palette card no longer shows a check since the ring already
marks it. The settings sidebar search bar, nav pills, and search results
get a little side padding, and the Settings label lines up with the pill
text.

* Unsloth: indicator restyle, sidebar menu customization, edge fade toggle

- Derive focus and selection rings from the border color so indicators
  stay 1px and adapt to every theme and palette
- Suppress mouse focus rings except on pressed controls to remove the
  selection flash on the avatar and palette pickers
- Defer settings panel rendering so the active nav pill updates instantly
- Customizable sidebar user menu with drag to reorder and shortcuts to
  the settings tabs
- Grey hover for the standard light palette instead of green
- Borderless controls in dark mode with fill based focus states
- Profile picture: no picture option, pencil edit icon, atomic selection
- Font dropdowns: narrower triggers and the resolved default shown as
  Inter Variable (Default)
- System prompt border darkens on focus
- New appearance setting to swap edge fades for thin divider lines
- Move the theme bootstrap to an external script to satisfy CSP

* Unsloth: harden theme boot and Firefox scroll container focus

- Guard the theme and palette storage reads separately so a blocked
  localStorage (private browsing) still resolves a mode from the OS
  preference instead of skipping the boot entirely
- Firefox makes scrollable containers keyboard focusable and drew its
  3px UA outline on them; swap it for the app's soft 1px indicator

* Unsloth: make the UI and code font settings reach the font utilities

The theme block declared the sans and mono stacks as literals, so
Tailwind inlined them into every font-sans and font-mono utility at
build time and the runtime overrides from Settings > Appearance never
applied. Reference the :root tokens instead, matching how the color
tokens already work.

* Unsloth: in-dropdown font upload, accent meters and avatar, naming cleanup

- Move font importing into each font dropdown: Upload and Select folder
  sit side by side under the list, imported fonts get an inline remove,
  and the standalone Import font row is gone
- Uploads reuse fonts the user already has (bundled, imported, or
  installed, matched by file name with style suffixes stripped) instead
  of embedding a duplicate copy; only new fonts are embedded
- Folder scan lists font files from a picked folder in every dropdown
  for the session; picking one imports it through the same path
- Fallback avatar uses the control accent with a readable foreground
  instead of the neutral primary that rendered black outside standard
- Monitor bars, progress defaults, sliders, and usage meters use the
  control accent; warning and danger tiers stay amber and red
- User facing strings that called the app just Studio now say Unsloth
  in all four locales, keeping Unsloth Studio and LM Studio intact

* Unsloth: left align the font upload actions and divide them

Upload and Select folder now read from the left like the list items,
with a short vertical rule between the two.

* Unsloth: keep sliders neutral and the chat greeting on Hellix

- Sliders are controls, not meters, so their fill goes back to the
  neutral primary instead of the palette accent
- The base h1 rule reads --font-heading with !important and the chat
  thread root resets that variable to the sans stack, which pulled the
  greeting off Hellix; restore the stack on the greeting element

* Unsloth: move the None avatar cell last and keep footer actions on one line

- None sits after the sloth pictures instead of leading the grid
- Upload shrinks to its label so Select folder no longer wraps

* Unsloth: size the folder action to its label

Both footer actions now hug their content so the hover pill does not
stretch across the leftover row width.

* Unsloth: separators only between unrelated settings clusters

Rows inside a titled section are related, so the per row divide-y is
gone from SettingsSection. A SettingsGroupDivider marks the two real
boundaries in the theme section (colors to fonts, fonts to contrast)
and the Clear all chats row gets its destructive border back now that
divide-y no longer draws one for it.

* Unsloth: balance the two font upload actions

Both actions share the footer row evenly again; nowrap keeps Select
folder on one line at the narrower width.

* Unsloth: drop the theme section dividers and split the chat menu groups

The colors, fonts, and contrast rows read fine without rules, and the
chat menu gains its one real boundary between the pin toggles and the
disclaimer rows.

* Unsloth: normalize oversized sidebar menus and reject newline font data URLs

Two backend validation fixes in PersonalizationCustomization:

- sidebarMenu refused any list longer than the number of distinct ids
  because Field(max_length) is enforced before the dedupe validator runs.
  A stale or duplicated payload that would normalize to one entry per id
  was rejected outright, defeating the normalizer that exists for exactly
  that case. Cap the incoming list at a generous multiple so it reaches
  the validator; a pathologically long list is still refused.

- The imported font dataUrl validator used re.match on a pattern ending
  in $, which also matches just before a trailing newline, so
  "data:font/woff2;base64,AAAA\n" passed even though the frontend JS
  pattern rejects it. Use re.fullmatch for parity.

Adds covering tests for both.

* Unsloth: preview fonts in their own typeface and slim the color pills

- Every font dropdown entry, the default item, and the closed trigger
  render in the font they name, falling back to the UI stack for
  families the browser cannot resolve
- Color swatch pills drop from 36px to 28px so they sit closer to the
  row label height

* Unsloth: drop the font row and theme section descriptions

The labels carry the meaning on their own; the mode switching note in
particular read long and confusing.

* Unsloth: let the chat greeting follow the heading font setting

The greeting stays on Hellix by default but adopts a chosen heading
font through a --custom-heading-font variable the applier sets only
while an override exists, so the thread root's sans reset for chat
prose no longer hides the user's pick from the greeting.

* Unsloth: divide the theme section clusters and align the color pill height

Separators return between colors and fonts and between fonts and
contrast, and the color pills share the 32px height of the font
dropdown triggers.

* Unsloth: color pills at half the dropdown width

Fixed w-24 against the w-48 font triggers, with tighter padding so the
hex value still fits.

* Studio: update dep-removal test after next-themes was replaced

The frontend no longer declares next-themes or imports it in src (it was
replaced by the custom theme store and boot script), so the checker now
reports its removal as a safe no-op. The C1 and C8 fixtures in
test_frontend_dep_removal.py still asserted next-themes was a used
dependency, which fails the studio frontend CI dependency-removal safety
check. Update C1 to expect a no-op PASS and drop next-themes from the C8
expected failures so the suite matches the checker's correct output.

* Studio: remove unused ageLabel and exportCollectionJsonl helpers

* Studio: fix blocked-storage theme desync, search jump race, font validation

- theme-store.ts: keep an in-memory currentTheme/currentPalette so a selected
  value survives when localStorage is blocked (private browsing). The snapshots
  previously re-read empty storage and reverted React state to the default while
  the DOM already changed. The matchMedia handler no longer re-reads storage, so
  it cannot clobber the in-memory choice; cross-tab storage events still adopt.
- settings-dialog.tsx: the search jump waited a single fixed 60ms for the
  deferred tab panel to render, then silently missed under render lag. Retry
  across animation frames until the target row exists, then scroll and flash.
- settings.py: apply the font-name character check to the four selected-font
  fields (uiFont/headingFont/chatFont/codeFont), and forbid backslash, comma,
  slash and control characters so a name cannot escape the quoted CSS
  font-family or smuggle extra fallbacks. Adds covering tests.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix appearance customization edge cases for PR #7077

- Reset all local preferences now also clears palette and appearance customization
- Number input wrapper keeps full width so fields fill their flex/grid cell, and the stepper stays pinned to the field edge
- Number stepper snaps to the min anchored step grid like the native spinner instead of leaving a step-invalid value
- Code font now applies to chat code fences and inline code via a dedicated token
- Reduce motion (on/off) is honored by onboarding/tour confetti and the theme toggle view transition
- Re-importing a font under the same name with new bytes now swaps the FontFace
- Keep local customization when a synced record predates the customization field, and re-push it

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Align client font name sanitization with server validation for PR #7077

sanitizeFont now strips the same characters the backend _FONT_NAME_FORBIDDEN
rejects (backslash, slash, comma, backtick) plus control chars, so a locally
chosen font name can no longer pass the client but fail the personalization PUT
and silently stall appearance sync.

* Address follow-up review items for PR #7077

- Number input wrapper carries React Flow interaction classes (nodrag/nopan/nowheel) so clicking the stepper arrows increments instead of dragging the node
- Preserve local palette and greeting-sloth toggle when the synced record predates those fields, and re-push them, mirroring the customization handling (new paletteSaved and greetingSlothSaved response flags)
- Add settings-search scroll targets (data-settings-label) for the Profile title, description, display name, nickname, and avatar shape rows

* Preserve absent personalization fields on PUT for PR #7077

A stale client that omits palette or customization previously had those
defaults materialized by model_dump() and persisted, which flipped
paletteSaved/customizationSaved to true and defeated the legacy detection.
The PUT now dumps only the request's set fields and merges them onto the
stored record, so omitted fields keep whatever was already stored.

* Persist theme and palette via a fixed allow-list for PR #7077

The theme/palette values reach setTheme/setPalette from the authenticated
personalization sync, which made the CodeQL clear-text-storage query treat
writing them to localStorage as storing sensitive data. Store a re-derived
literal from a constant map instead, so a plain UI preference is not tracked
as sensitive; behavior is unchanged.

* Harden imported-font handling for PR #7077

- syncImportedFonts: a rejected FontFace.load() only clears the registry entry
  if it still points at that face, so a same-name re-import while the old load
  was pending is no longer untracked/leaked.
- Cap imported-font names to the backend length (100) so an over-long name can
  no longer pass the client but fail the personalization PUT and stall sync.
- Add a backend test that a stale PUT preserves an existing stored palette and
  customization (not just that absent fields stay absent).

* Return the merged personalization record from PUT

The PUT /personalization handler returned the request payload, which
Pydantic had already filled with defaults for any field the client
omitted. A partial or stale write (for example a client sending only
theme) therefore got back a response that contradicted both storage and
the next GET: preserved fields like palette and the custom font showed
their defaults instead of the stored values.

Return model_validate(merged) so the response mirrors what was stored.
The stored record is still the full merged dict, so legacy fields the
model does not know about are preserved as before.

* Fix small UI and keyboard-focus defects in appearance settings

- Settings search now scrolls to the result within its destination tab
  instead of a same-named row in the previously rendered deferred tab
  (for example "Storage" and "Models folder" appear in both General and
  Resources).
- The reduce-motion segmented control honors its own Off/On/System choice
  by reading useReducedMotionConfig instead of the OS-only useReducedMotion.
- The color picker saturation/value area is operable by keyboard, so the
  role="slider" surface responds to the arrow keys it advertises.
- Profile avatars and palette cards show a visible keyboard focus ring
  again.
- Guard the persisted appearance-customization write so a blocked or full
  localStorage does not throw out of a store action, matching the theme
  store.
- Import the appearance store symbols from the settings feature barrel.

* Tighten appearance fix comments

---------

Co-authored-by: shimmyshimmer <shimmyshimmer@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
2026-07-14 05:12:08 -07:00
oobabooga
5de668926c
Studio: make Stop interrupt a llama.cpp generation stalled mid-stream (#7117)
* Studio: make Stop interrupt a llama.cpp generation stalled mid-stream

* Studio: tighten stream-cancel comments

---------

Co-authored-by: danielhanchen <danielhanchen@users.noreply.github.com>
2026-07-14 05:11:56 -07:00
Long Yixing
c80e7d317a
fix(studio): prevent auth monitor reload loop (#7118) 2026-07-14 05:04:28 -07:00
Daniel Han
2f3eae9846
Studio: resolve llama.cpp prebuilts via the release-assets CDN to avoid GitHub API rate limits (#7086)
* Studio: resolve llama.cpp prebuilts via the release-assets CDN to avoid GitHub API rate limits

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Studio: resolve manifest-named prebuilt assets on the download-host fast path

Add tag-pinned CDN URLs for any manifest artifact whose hash is keyed under an
upstream-tag alias in the checksum asset, so the fast path resolves the same
assets the API path does. Cover the resolve body directly (only download_bytes
stubbed) and soften the doc's validation-equivalence wording.

* Studio: pin llama.cpp fast path to the releases/latest redirect tag

Derive the authoritative latest tag from GitHub's /releases/latest redirect
target instead of trusting the checksum asset's self-reported release_tag, so the
existing release_tag cross-check in parse_approved_release_checksums is a real
check again: a stale or mis-tagged checksum asset now falls back to the API. Pin
every fast-path URL to that tag. Fall back to the API on a manifest 404 as well,
since an in-progress release can publish the checksum asset before the manifest,
matching the sha256 404 handling. Document the releases/latest (created_at /
make_latest) versus published_at ordering divergence and why it is an accepted,
mitigated tradeoff.

* Studio: drop the llama.cpp prebuilt-resolution doc

Remove studio/docs/llama-cpp-prebuilt-resolution.md and the docstring pointer to
it; the resolution rationale (the created_at/make_latest vs published_at ordering
nuance) stays inline in _download_host_latest_release_tag.

* Studio: tighten llama.cpp download-host fast-path comments

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-14 03:30:17 -07:00
Nilay
601155114d
Studio: persistent stdio MCP sessions so server state survives across tool calls (#7080)
* Studio: persistent stdio MCP sessions so server state survives across tool calls

call_tool_sync spawned a fresh stdio subprocess per tool call
(keep_alive=False) and tore it down when the call returned, so any stateful
MCP server lost its state between calls: with @playwright/mcp,
browser_navigate opened the page in one subprocess and
browser_take_screenshot ran in a brand-new one, screenshotting about:blank.

Keep one connected client per (command, env) on a dedicated event-loop
thread and reuse it across calls:

- idle sessions are reaped after 5 minutes (in-flight calls excluded) and
  everything closes at exit, preserving the old design's no-orphans property
- a dead subprocess is detected via is_connected() and retried once on a
  fresh session; tool-level errors leave the session alone
- cancel and timeout semantics are unchanged, and a timed-out call does not
  tear the session down
- updating a server's endpoint/env/enabled state or deleting it closes its
  live session
- HTTP/SSE servers stay one-shot per call

* address review feedback

* fix stdio session cleanup

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* address review: per-thread MCP scope, close-during-connect and abort races

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* address review: unblock no-limit calls on close, drain borrowers before close, scope closes to url+env

* don't retry sessions closed by config changes, re-verify server row before caching, keep env secrets out of generation keys

* fail fast on connect errors and make the stdio key-lock wait cancellable

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* quote MCP scope parts so IDs with colons can't collide

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* serialize per-session stdio calls, span one timeout budget across connect and call, hash urls in generation keys

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Harden persistent stdio MCP sessions: crash recovery, concurrency, scoping

- Evict a stdio session on any transport-level (non-ToolError) call failure and
  do not replay it, so a mid-call subprocess crash can no longer poison the scope.
  Never gate liveness on Client.is_connected() (it only reports that a session
  object exists, not that the subprocess is alive); add a version-adaptive
  dead-transport probe that works on fastmcp 3.0.2 and newer.
- Re-check closed/defunct/config and transport liveness after acquiring the call
  lock, and retire a session before releasing the lock, so a queued same-scope
  caller never reuses a session that another caller's timeout already retired.
- Force a ProactorEventLoop on Windows so the stdio transport can always spawn
  subprocesses regardless of the active event-loop policy.
- Scope stdio sessions per conversation: require thread_id to persist, and tag
  the fields so a session_id and a thread_id with the same value cannot collide.
  A session_id alone is project-wide, so it now falls back to a safe one-shot
  session instead of sharing browser/DB/REPL state across conversations.
- Forward thread_id on the Anthropic Messages path.
- Treat timeout=None as unlimited on connect and the key lock (was capped at 60s).
- Bound the session cache (default 32, override via
  UNSLOTH_STUDIO_MAX_STDIO_MCP_SESSIONS) with LRU eviction of idle sessions.
- Run config_check on cache hits, and log a redacted exe#digest label instead of
  the raw command so credentials in argv never reach the logs.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Trim the stdio MCP session cache on release and skip close-generation for HTTP servers

Two fixes from review of the persistent stdio session lifecycle:

- Re-enforce the session cap when a session goes idle. A concurrent burst of
  distinct-scope calls can overshoot the cap while every cached session is busy
  (insert-time eviction only reclaims idle sessions), and the overshoot used to
  persist until the 5-minute idle reaper. _release_stdio_session now trims the
  idle overshoot back within the cap, without ever evicting an in-flight call.
- close_stdio_sessions() now no-ops for a specific non-stdio (HTTP/SSE) url.
  Those transports are never cached as stdio sessions, so calling it on every
  HTTP server update or delete used to accrue an unbounded close-generation entry.

Both are covered by regression tests that fail before the change and pass after.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Keep the live stdio MCP session across a display-name rename

The edit dialog resends url, headers, and use_oauth unchanged whenever a
server is saved, so gating the tool-cache invalidation and stdio session
close on field presence dropped the persistent process on a plain rename
or any no-op edit. Gate on a real value change against the stored row so
only a genuine endpoint, auth, or enable change closes the session.

Regression tests: a rename that resends unchanged url/headers/oauth keeps
the session; a real command change still closes it.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Tighten comments in the stdio MCP session lifecycle

Collapse a few verbose comments to fewer lines with the wording preserved,
and drop one that restated the clear_oauth_tokens_async docstring. Comments
only; no code change.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: danielhanchen <danielhanchen@gmail.com>
2026-07-14 02:28:43 -07:00
Daniel Han
744b59f04a
scan_packages: baseline sentencepiece dup2 finding after upstream reindent (#7120)
The supply-chain scan gates on non-baselined CRITICAL/HIGH findings. A newer
sentencepiece release reindented the stdout/stderr fd-redirect helper in
sentencepiece/__init__.py (the os.dup2 pair the heuristic flags as a
reverse/bind-shell pattern), moving it from L1221/L1226 to L772/L777 and
changing its leading indentation.

The baseline key is (package, package-relative file, check, evidence_hash),
where evidence_hash is over the matched code with the L<NN>: markers stripped
but the code's own indentation preserved. The reindent therefore changed the
hash (bba233.. -> 65b5a11c..), so the existing entry no longer suppressed the
finding and it resurfaced as a blocking CRITICAL in the hf-stack and studio
scan legs.

Add the new indentation variant to the allowlist. The calls are sentencepiece
redirecting stdout/stderr file descriptors to capture its C++ logs, not a
shell; no socket or networking is involved. The old L1221 entry is kept so
both versions stay covered.
2026-07-14 01:38:50 -07:00
Michael Han
6e375a5b17
Studio: add French, German, Spanish, Hindi, Arabic, Russian and Korean display languages (#7076)
* Studio: add 7 display languages, complete and fix existing locales

Adds fully translated French, German, Spanish, Hindi, Arabic, Russian and
Korean locales. Fills in all missing keys for zh-CN (113), ja (71) and
pt-BR (47), fixes translation errors found in review, and reorders the
language dropdown by popularity. All overlays pass check-parity with zero
missing keys and zero placeholder mismatches.

* Studio: default display language to auto detect

The language preference now defaults to auto and resolves against the
browser language list, with exact tag match first and language subtag
match second (pt-PT resolves to pt-BR, zh-TW to zh-CN). Auto detect is
the first dropdown option and is translated in every locale. Explicit
choices still persist and sync; personalization sync now round trips
the preference instead of the resolved locale so auto stays auto across
devices. Auto mode also follows browser languagechange events.

* Studio: guard import.meta.env in translate for non-Vite contexts

translate() read import.meta.env.DEV directly, which throws when the
module runs outside Vite (SSR or Node tooling). Optional-chain it so the
dev-only warning is skipped and translation still works everywhere.

* Studio: RTL for Arabic, translate recipes, keep Traditional Chinese off zh-CN

- Sync document dir from a per-locale dir field so Arabic mirrors the
  layout instead of rendering RTL text in an LTR shell.
- Translate the recipes nav label in fr, de, ko and hi to match the
  other locales (Recettes, Rezepte, and native forms).
- Detection no longer maps Traditional Chinese (zh-Hant / zh-TW / zh-HK /
  zh-MO) to Simplified zh-CN; those tags fall through to the next
  preferred language. Simplified tags (zh, zh-CN, zh-SG, zh-Hans) still
  resolve to zh-CN.

* Studio: don't treat legacy synced English as an explicit language pick

The old sync serialized the resolved locale on every save, so existing
profiles carry appearance.language 'en' even when the user never chose a
language. Hydrating that as a pinned locale forced non-English browsers
back to English under the new Auto detect default. Payloads now carry
version 2 (the preference itself); on hydrate a version 1 'en' maps to
auto, while explicit picks and all version 2 values are kept as-is.

* Studio: persist only known language codes from the locale table

normalizePreference now returns a value re-derived from the LOCALES keys
instead of the raw input. It stays functionally identical (the stored
value was already whitelisted) but makes it explicit that only known,
non-sensitive language codes are written to localStorage, and clears a
false-positive clear-text-storage scan on the persistence path.

* Studio i18n: fix Train label transliteration and tidy locale consistency

- ja and hi: the nav and route Train label used the railway transliteration
  (トレイン and ट्रेन); switch to the training term already used everywhere
  else in each file (トレーニング, ट्रेनिंग).
- zh-CN: keep VRAM in English to match every other locale and the PR's own
  keep-English rule, and drop an extra clause added to the upload size hint
  so it matches the English source.
- hi: translate Recents to हाल के in the export and import section to match
  the sidebar label, and point users to the Configure tab by its translated
  name (कॉन्फ़िगर).
- ru: reword the preview sharing hint to avoid the "disable to disable"
  repetition.

i18n parity and the type checked build stay green.

* Studio i18n: keep Arabic layout LTR until physical-direction CSS is converted

Setting ar to dir rtl only mirrors the flex based shell, sidebar and
settings dialog. The shared select, dialog and dropdown primitives use
physical-direction utilities (right-2, top-5 right-5, ml-auto) that do not
flip under dir rtl, so chevrons, close buttons and check marks land on the
wrong side. Keep Arabic on an LTR layout for now, matching the original plan
in this PR. Arabic text still renders right to left per element via bidi and
chat content keeps dir auto, so nothing regresses. Full layout mirroring can
follow once the physical-direction classes are converted to logical ones.

* Studio i18n: do not let a generic zh after a Traditional tag pick Simplified

navigator.languages can be a list like ['zh-TW', 'zh', 'en-US']. The zh-TW
pass already falls through, but the bare zh then reached the language-subtag
match and selected zh-CN, so Traditional Chinese users still got Simplified
and the guard was defeated. detectLocale now remembers when a Traditional
tag was seen and skips a later bare zh, so detection keeps falling through to
the next non-Chinese language. A lone bare zh, and explicit zh-CN or zh-Hans
fallbacks, still resolve to Simplified as before.

* Studio i18n: collapse two locale comments to a single line

The Arabic dir note in messages.ts and the bare-zh note in
locale-store.ts were two lines each; tighten each to one. Comment
only, no behavior change.

* Studio i18n: translate Hindi strings that were left in English

Seventeen hi.ts labels stayed in English while all the other locales
translated them: the training parameter labels (Grad Accum, Grad Norm,
Grad Checkpoint, Eval Loss, Clip p95/p99, Seed, Continued Pretraining),
the API example labels (curl/Python/JavaScript + tools/advanced),
Hugging Face token, the VRAM estimate and the training terminal start
line. Parity only checks key/placeholder presence so it did not catch
these. Brand and technical tokens (curl, Python, VRAM, Loss, p95/p99,
Hugging Face, unsloth) stay in English as elsewhere.

---------

Co-authored-by: danielhanchen <danielhanchen@gmail.com>
2026-07-14 01:38:04 -07:00
Nilay
fea7d9ba34
Studio: render image content returned by MCP tools (#7081)
* MCP image handling

* clean upg

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Studio: return MCP error results so image content is not dropped

FastMCP client.call_tool raises ToolError by default on an is_error
result, so it never reaches _flatten_result and any returned image is
dropped. Pass raise_on_error=False so error results flow through
_flatten_result and keep their images. Transport failures still raise
and hit the existing handler. Add a regression test for the real path.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Studio: accept raise_on_error kwarg in MCP test fake clients

The call_tool_sync fix passes raise_on_error=False to client.call_tool.
Update the fake MCP clients patched into mcp_client._client so their
call_tool signatures accept the keyword, keeping the stdio/servers MCP
test suites green.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Studio: tighten MCP raise_on_error rationale comments

* Studio: only strip MCP image sentinel when suffix is a valid image envelope

* Studio: validate MCP image envelope in chat adapter and keep base64 out of exports

* Studio: sanitize MCP images in all export formats and fall through to sandbox parser on invalid marker

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: danielhanchen <danielhanchen@gmail.com>
2026-07-14 00:30:21 -07:00
oobabooga
ed42702730
Probe xformers support on sm_120 instead of disabling it by version (#6828) 2026-07-14 00:01:25 -03:00
oobabooga
a5eb10abc9
Studio: Add rename to project chat rows (#7005) 2026-07-13 20:52:30 -03:00
oobabooga
014d08c763
Studio: install torchao Windows ROCm stub in the inference worker (#7000) 2026-07-13 19:20:25 -03:00
oobabooga
76d7088e0a
Studio: Show Run button for downloaded non-GGUF models in the Model Hub (#7001) 2026-07-13 19:18:20 -03:00
oobabooga
f60b982a09
Studio: Fix torch_dtype deprecation warning on startup and ASR load (#6999) 2026-07-13 17:34:25 -03:00
oobabooga
85f5292097
Studio: resync model state after a llama.cpp update unloads it (#6998) 2026-07-13 15:43:13 -03:00
Long Yixing
a337c72753
Fix Studio auto-titles for reasoning models (#7098) 2026-07-13 15:13:50 -03:00
Long Yixing
2573dbdd6b
fix(studio): use writable recipe artifact path (#7044) 2026-07-13 15:08:45 -03:00
Long Yixing
cc8599207c
Fix Studio user-message overflow for long unbroken text (#7100) 2026-07-13 14:42:40 -03:00
Daniel Han
c570180a32
Tighten Studio instruction-file cleanup boundaries (#7097)
* Handle linked instruction files in Bash cleanup

* Limit instruction cleanup to managed dependencies

* Make Bash cleanup test portable

* Run junction cleanup regression on Windows

* Keep instruction cleanup CI focused
2026-07-13 01:46:23 -07:00
Daniel Han
9e77c1e663
Studio: remove AGENTS.md and CLAUDE.md from install artifacts (#7096)
* Studio: remove AGENTS.md from install artifacts

* Studio: prune CLAUDE.md from install artifacts

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix Studio instruction cleanup edge cases

* Trim Studio cleanup comments

* Make Studio cleanup safe on PowerShell 5.1

* Fix Studio cleanup ownership boundaries

* Simplify Windows link detection

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-13 00:15:04 -07:00
Daniel Han
ca979e9643
Studio: add UNSLOTH_SKIP_AUTOSTART installer flag (#7093)
* Studio: add installer autostart opt-out

* CI: run installer autostart tests cross-platform

* Tests: combine Studio installer skip flags
2026-07-12 21:23:14 -07:00
Daniel Han
2a22da9fd7
Studio: startup loading banner and mute the benign bitsandbytes ROCm warning (#7085)
* Studio: startup loading banner and mute the benign bitsandbytes ROCm warning

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* studio: shorten startup banner wording

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-12 18:19:33 -07:00
WinkleMad
935474c20a
Fix SyntheticDataKit.chunk_data emitting chunks over max_tokens (#7073)
* Fix SyntheticDataKit.chunk_data emitting chunks over max_tokens

The multi-chunk path built boundaries from np.linspace(..., n_chunks), but
pairing boundaries[:-1] with boundaries[1:] turns N points into N-1 ranges,
so it produced one fewer, oversized chunk: every chunk exceeded max_tokens
and a document just over the threshold came back as a single unsplit chunk.
Use n_chunks + 1 points so exactly n_chunks ranges are emitted, each within
max_tokens.

Also base n_chunks on the non-overlapped span: consecutive chunks overlap by
overlap, so covering length needs ceil((length - overlap) / stride) chunks, not
ceil(length / stride). The looser count over-counted by one just past a stride
multiple (a 673-token doc became 3 chunks of ~267 instead of 2 of ~369),
emitting an extra redundant chunk. Coverage and overlap are unchanged and every
chunk still stays within max_tokens.

* Condense chunk_data comments and clarify over-split test for PR #7073

---------

Co-authored-by: danielhanchen <danielhanchen@gmail.com>
2026-07-12 05:06:11 -07:00
Daniel Han
275bad1f64
Studio: fix the manual response-template markers that never match their rendered templates (#7062)
* Fix broken manual response-template markers in Studio's fallback table

Six template families in TEMPLATE_TO_RESPONSES_MAPPER shipped markers that
never match what their chat templates actually render, so the manual
train_on_completions path masked every assistant token and the run died on
the all-labels-masked safety net:

- mistral, llama: '[INST] ' / ' [/INST]' - the surrounding spaces fold into
  the neighbouring tokens ('[INST]'/'[/INST]' are single special tokens in
  Mistral v0.3, SentencePiece pieces in Llama-2), so the padded strings
  never match. Now '[INST]' / '[/INST]'.
- starling: trailing space after 'GPT4 Correct Assistant:' folds into the
  next content token. Now no trailing space.
- glm: '[gMASK]<sop>' renders once at text start, never before later user
  turns, and '<think>' is generation scaffolding rendered as a lone
  '</think>' on non-final turns. Now '<|user|>' / '<|assistant|>'.
- qwen3-thinking: '<think>' is stripped from non-final assistant turns
  (Qwen3-Thinking-2507) and never rendered by QwQ. Now the bare assistant
  header, matching the other qwen entries.
- zephyr: role tags are plain text and SentencePiece tokenizes them
  differently at text start than after '</s>' + newline mid-conversation;
  the markers need the leading newline anchor. Now '\n<|user|>\n' /
  '\n<|assistant|>\n'.

Validated token-level on each family's representative tokenizer with a
two-turn fixture plus system message: user and system content fully masked,
every assistant turn trained, and the final EOS label never -100. The
fixed mistral, llama, starling and glm markers produce labels identical to
zoo auto-detection; qwen3-thinking differs only in one turn-separator
newline token. All 22 unchanged entries produce byte-identical labels to
before this change.

Adds tests/test_response_template_markers.py pinning the fixed and key
unchanged marker literals (dependency-free) plus token-level masking checks
that skip when tokenizers or unsloth_zoo are unavailable offline.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Close tokenizer config handle and read it as UTF-8

Chat templates in tokenizer_config.json are rarely ASCII-only, so the
default locale codec could fail the GLM fallback loader on Windows.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Tighten comments

* Anchor the llama marker on <s> and harden the marker test

On transformers 5.x llama-2 tokenizes [INST] after <s> as a bare left
bracket while the standalone encoding gives the space-prefixed piece, so
the unanchored marker missed every turn boundary and later user turns
leaked into training; 4.57 masked this. Anchoring on <s>[INST] matches
both tokenizations, verified token-level under 4.57.6 and 5.5.0.

The test now unwraps the BatchEncoding that apply_chat_template returns
on 5.x before indexing, and the latent trailing spaces in the unreachable
unsloth and vicuna entries are dropped for table consistency.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-11 21:29:19 -07:00
Daniel Han
f899834e58
DeepSeek-V4: eager attention and trainable FP8 grouped experts (#7042)
* DeepSeek-V4: eager attention and trainable FP8 grouped experts

deepseek_v4 ships a custom attention that is not compatible with the sdpa
and flash paths, so add it to _EAGER_ONLY_PREFIXES to load with eager.

Its fused experts load as FP8GroupedLinear, whose forward calls a grouped
matmul kernel with no autograd formula, so loss.backward() fails during
finetuning. Patch the forward to dequantize the frozen fp8 weight and run a
differentiable grouped matmul while training, keeping the fused fp8 kernel
for inference.

* DeepSeek-V4: exclude sdpa/flash and stream fp8 grouped backward

Add deepseek_v4 to _SDPA_EXCLUDED_MODELS and _FLASH_EXCLUDED_MODELS so an
explicit attn_implementation=sdpa/flash request downgrades to eager instead of
raising (the model has no sdpa/flash kernel), matching the eager-only default.

Replace the FP8GroupedLinear training bmm with a custom autograd Function that
saves only the fp8 weight + scale rather than a full bf16 dequantized copy, so
no dequantized grouped weight is retained per layer, and unwrap tensor-parallel
shards before dequant. Bit-exact forward and grad with the previous path.

* FP8 grouped: consistent checkpointing math and block-size-aware dequant

Gate the differentiable training path on self.training rather than
torch.is_grad_enabled(), so a gradient-checkpointed segment runs the same bmm
math in its no-grad forward and its grad recompute instead of mixing the fused
fp8 kernel with bmm.

Dequantize with the layer's own block_size via _blockwise_weight_dequant_any_shape
so non-128 or rectangular fp8 blocks are scaled correctly instead of assuming
128x128.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Tighten comments

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-11 19:33:05 -07:00
Daniel Han
9fa6fd40e1
scripts: refresh scan_packages allowlist baseline (#7078)
New releases of huggingface-hub (1.23.0) and openai (2.45.0) shifted or
added polling loops that the C2 polling/beaconing check flags, failing
all three pip scan-packages shards (studio 1, hf-stack 1, extras 3 new
CRITICAL findings) org-wide including on main.

Regenerated with scan_packages.py --write-baseline per CI shard (same
shard-to-requirements mapping and --with-deps as security-audit.yml)
and merged. All entries were manually reviewed at the resolved versions:

- huggingface-hub hf_api.py: create_repo 409-concurrency retry loop
  body changed in 1.23.0; refreshed evidence hash. The loop POSTs to
  the canonical Hub endpoint and retries only on a specific conflict
  error. Benign client retry.
- openai beta/threads/runs/runs.py: create_and_poll run-status helper
  refactored in 2.45.0 (Assistants deprecation annotations); refreshed
  evidence hash. Documented polling helper against api.openai.com.
- openai beta/responses/responses.py: new beta websocket client whose
  __aiter__ yields server events until the connection closes. New
  entry; standard event-stream iterator, not beaconing.
- openai resources/responses/responses.py: evidence line number
  refreshed only, hash unchanged.

The two dropped entries are the pre-refactor hashes of the same two
loops above; they no longer occur at the resolved versions. Verified
locally: all three shards exit 0 with 0 unsuppressed CRITICAL/HIGH
(hf-stack 120, studio 151, extras 99 suppressed).
2026-07-11 08:15:43 -07:00
Daniel Han
6412efd7d9
Studio: auto-detect completion masking markers, stop silent full-sequence training (#7054)
* Auto-detect completion masking markers with template table fallback

Studio's train_on_completions previously relied only on the hardcoded
MODEL_TO_TEMPLATE_MAPPER / TEMPLATE_TO_RESPONSES_MAPPER tables and
silently disabled masking when a model was not in the table, so unmapped
models (LFM2-8B-A1B, DeepSeek, and others) trained on full sequences
without telling the user. Several mapped templates (glm, mistral, llama,
starling, zephyr, qwen3-thinking) also carried markers that mask every
assistant token, which made every row drop in the post-masking filter.

Both training callsites (CUDA trainer.py and MLX worker.py) now share
utils.datasets.completion_masking.apply_completion_masking:

- Try unsloth_zoo chat template auto-detection first; it raises loudly
  when the template cannot be parsed and never masks the EOS token.
- gpt-oss models keep their manual markers so non-final assistant
  <|end|> tokens stay trained, matching current behavior.
- If auto-detection raises, fall back to the template table exactly as
  before.
- If the table also misses, emit an explicit user-visible warning that
  completion masking could not be applied and full-sequence training
  will occur, instead of a quiet log line.

The >30 percent dropped-rows safety net in trainer.py now guards the
auto path as well. Table consumers for inference and chat templates are
unchanged. Validated against one representative tokenizer for every
template in TEMPLATE_TO_RESPONSES_MAPPER plus the unmapped models:
no template regresses; unit tests cover the four decision paths.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Restrict masking fallback to marker detection failures

The auto branch wrapped the whole train_on_responses_only call, so a real
failure while applying the masking (dataset map, tokenization) was treated
as a detection miss and training silently proceeded on full sequences.
Detect markers separately via get_chat_template_parts (test seam via
detect_fn), then apply them with errors propagating, matching the manual
path. Tokenizers with preset unsloth marker attrs skip detection and call
bare so zoo reuses the stored parts.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fail the run when applying completion masking raises

The helper already falls back internally on detection failures and returns
applied=False on a double miss, so an exception reaching the callsites is a
real failure applying the masking. Remove the callsite catches that
downgraded it to full-sequence training; the run now fails visibly instead.

Also use the explicit re-export alias form in utils/datasets/__init__.py for
the two new names, satisfying the import-hoist source lint.

* Import completion masking from its submodule

The import-hoist source lint counts only real name loads, so package-level
re-exports of the two new names cannot satisfy it. Import
apply_completion_masking from utils.datasets.completion_masking directly at
both callsites and leave utils/datasets/__init__.py untouched.

* Completion masking: gpt-oss renames and MLX raw/alpaca parity

Renamed or private gpt-oss checkpoints are name-detected as gpt-oss but miss
the exact-name table; default them to the gpt-oss template markers instead of
falling through to full-sequence training.

Gate the MLX masking call on not raw_text_mode and format_type != alpaca,
mirroring the CUDA path: raw/CPT text has no chat turns to mask and
Alpaca-rendered text lacks the tokenizer's chat markers.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Define raw_text_mode outside the MLX feature-detect block

With an older zoo lacking the append_eos config field, the masking
gate referenced raw_text_mode before assignment. Hoist the assignment
above the feature detection so both consumers see it.

* Gate MLX masking on the formatter's resolved format

format_type auto can resolve to alpaca or raw text; the masking skip
checked only the requested value, so auto-detected Alpaca data got
chat-template markers applied to rendered prompt text. Track the
final_format returned by format_and_template_dataset and gate on it,
matching the CUDA path.

* Unwrap the mlx-lm TokenizerWrapper before marker checks

The wrapper delegates plain reads to the wrapped HF tokenizer but hides
underscore attrs, so preset unsloth markers were invisible and detection
relied on the loader's call patch. Unwrap to the real tokenizer first,
as the zoo MLX resolver does.

* Tighten masking comments

* gpt-oss: auto-detect markers first like every other template

The quantized and BF16 gpt-oss checkpoints ship a chat template without
the channel final header, so the pinned manual markers match nothing
there and masking trained zero tokens. Auto-detection derives markers
from whichever template the checkpoint ships and keeps the final
terminator trained; the manual gpt-oss markers remain the detection
failure fallback, including for renamed checkpoints.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Tighten comments

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-11 05:13:45 -07:00
Daniel Han
97161c89d6
Studio: route models by CONFIG_MAPPING_NAMES instead of hardcoded tables (#7043)
* Studio: route models by CONFIG_MAPPING_NAMES instead of hardcoded tables

A model whose model_type is absent from an overlay's transformers cannot load
there, so a new MoE arch not yet in the tier tables gets routed to default and
fails (e.g. lfm2_moe, deepseek_v4). Add a static resolver that parses each
overlay's CONFIG_MAPPING_NAMES straight from source (AST only, no import, no
network, no trust_remote_code) and picks the lowest tier that ships the
model_type. Runs after the existing checks and only ever upgrades default, so
no existing routing changes and new archs no longer need a table edit.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Studio router: harden the CONFIG_MAPPING_NAMES resolver

- Resolve the default tier map from the base install, skipping any .venv_t5_*
  sidecar on sys.path, so an in-process 5.x activation cannot make a 5.x-only
  model look loadable by 4.x.
- Do not cache an overlay whose sidecar dir is absent, so a later call re-reads
  it once provisioned instead of serving a stale empty map.
- Also collect model types added via CONFIG_MAPPING_NAMES.update({...}) and
  **{...} unpacking, not just the literal assignment (5.10 uses both).
- Wrap the AST walk in the try/except so a malformed source can never crash tier
  resolution.
- Feed the mapping fallback from _load_config_json so a config served from the
  hub cache during a transient outage still routes new architectures.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Tighten comments

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-11 05:12:37 -07:00
Daniel Han
c3feac6160
Studio: route lfm2_moe (LFM2-8B-A1B) to transformers 5.3.0 (#7040)
LFM2-8B-A1B and any other lfm2_moe checkpoint were missing from the
transformers tier tables, so they fell through to the default 4.57.x
sidecar, which does not register lfm2_moe and errors with
"not supported yet in transformers==4.57.6". Only lfm2_vl was listed.

Add Lfm2MoeForCausalLM / lfm2_moe to the 5.3.0 tier (lfm2_moe is
registered in transformers 5.3.0). get_transformers_tier now returns
530 for LFM2-8B-A1B and the model loads and trains as expected.
2026-07-11 05:08:07 -07:00
oobabooga
d105bd7b42
Studio: detect Windows Intel GPUs via the registry before WMI (#7064) 2026-07-10 17:59:04 -03:00
oobabooga
7bfa209623
Studio: hint at Model auto-switch in the OpenAI "No model loaded" 400 (#7006) 2026-07-10 17:48:27 -03:00
Apoze
fef37cb25b
Studio: queue local GGUF OpenAI-compatible requests before llama-server (#7047)
---------

Co-authored-by: oobabooga <112222186+oobabooga@users.noreply.github.com>
2026-07-10 17:05:48 -03:00
Vineeth Sai
33119c9bf7
fix: guard remove_special_tokens against tokenizers without a BOS token (#7048) 2026-07-10 14:55:11 -03:00
Daniel Han
fbcd3fa511
CI: retry transient HTTP timeouts in Studio smoke probes (#7052)
* CI: retry transient HTTP timeouts in Studio smoke probes

The post() helper in the Studio inference smoke workflows does a single
urlopen with a 240s timeout against the local Studio server. On shared
runners this sporadically hits TimeoutError while the server is stalled,
failing the whole job for a transport hiccup; the same flake has recurred
across unrelated PRs on Linux and Windows (JSON/images and tool-calling
jobs) and passes on rerun.

Retry the probe up to 3 times on transport-level failures only
(TimeoutError, ConnectionError, non-HTTP URLError), 15s apart. HTTP
status errors still surface immediately, so genuine server failures are
unaffected. post_sse() is left unchanged: it has a 600s budget and has
not flaked.

* CI: retry only short probes so worst case fits the job budget

Some json-images calls pass timeout=600; three attempts there could spend
30 minutes in one step and hit the job's timeout-minutes instead of failing
with the Python error. Retry (3 attempts) only when timeout <= 300s, which
covers the observed flaky 180-240s probes; longer probes keep the pre-PR
single attempt.

* CI: give long smoke probes one capped retry

Round two of bounding the retries: timeout>300s probes previously got a
single attempt, so a transient stall in the 600s JSON-mode probes still
failed on first occurrence. Give them one retry with the attempt timeout
capped at 300s. Worst cases stay inside timeout-minutes: 240s probes
12.5 min, one 600s probe 15.25 min, the Windows JSON job's two long
probes 30.5 min against its 35 minute budget.
2026-07-10 03:07:39 -07:00
oobabooga
b0b8aea618
Clarify in README that -H 0.0.0.0 starts a public Cloudflare tunnel (#7007)
* Clarify in README that -H 0.0.0.0 starts a public Cloudflare tunnel

* Hedge tunnel URL wording and restore trusted-network caution

* Tighten the 0.0.0.0 tunnel note

* Drop trust-the-network caution from tunnel note

* Restore trusted-network note on the raw-bind sentence

* Use Cloudflare's quick tunnel terminology and consolidate the trust warning
2026-07-09 17:59:48 -07:00
alkinun
86602a5389
Studio: auto-load last used local model (#6966)
* Studio: auto-load last used local model

* Studio: handle missing GGUF quant in last-used autoload

* Studio: tighten last-used autoload handling

* Fix

* Honor last-used autoload settings

* Skip recording LoRA auto-loads

* Mirror auto-load runtime state

---------

Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
Co-authored-by: imagineer99 <samleejackson0@gmail.com>
2026-07-09 17:21:49 +01:00
Apoze
6a9b77ee37
Studio: harden OpenAI-compatible GGUF streaming (#6950)
---------

Co-authored-by: oobabooga <112222186+oobabooga@users.noreply.github.com>
2026-07-09 12:09:08 -03:00
Daniel Han
b5aef63c03
Studio: resolve the repo-root MTP drafter after the MTP/ GGUF rename (#7031)
* Studio: resolve the repo-root MTP drafter after the MTP/ GGUF rename

The Gemma 4 QAT GGUF repos renamed the higher-precision MTP/ subdir
copies from gemma-4-...-<quant>-MTP.gguf to mtp-gemma-4-...-<quant>.gguf,
so their basenames now start with the same mtp- prefix as the small
repo-root drafter (mtp-gemma-4-E4B-it.gguf).

The drafter selectors filtered candidates by a mtp- basename prefix and
took the first in sort order. With the new names the MTP/ copies also
match, and because MTP/ (uppercase) sorts before the lowercase root file,
selection flipped to the large BF16 copy under MTP/ instead of the root
drafter both functions document they should pick.

Restrict both selectors, and the companion byte estimate, to root-level
mtp-*.gguf so the MTP/ copies stay explicit-selection only:
- core/inference/llama_cpp.py _pick_mtp (loader auto-download)
- hub/utils/gguf_plan.py preferred_mtp_sibling (Hub variant plans)
- routes/inference.py _remote_gguf_companion_bytes (VRAM headroom)

Also reuse a drafter already in the local cache before downloading, so a
device that already holds a copy on disk does not re-fetch it.

Old-scheme names keep working (they have no root-level mtp- sibling to
mis-select). Adds regression tests for the new naming, both selection
paths, and the on-disk reuse.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Studio: gate MTP drafter cache reuse to offline mode

Reuse the cached drafter only when HF is offline. Online, route back
through _download_companion_gguf/hf_hub_download so the current revision
is checked (etag) and a changed drafter is refetched, matching the
offline-only cross-snapshot reuse already used for the main GGUF. This
avoids pairing freshly downloaded weights with a stale cached draft.
Make the reuse tests offline and add an online-skips-reuse test.

* Studio: prefer a root MTP drafter across all cached snapshots

Offline reuse scanned snapshots one at a time and returned the first
snapshot that held any drafter, only preferring root within it. A newer
partial snapshot with just the MTP/ copy could shadow the small root
drafter in an older snapshot. Collect drafters across all snapshots and
prefer any repo-root file before an MTP/ copy.

* Studio: keep newest-first snapshot order when reusing cached drafters

Collecting root candidates and sorting by absolute snapshot path could
pick a drafter from an older snapshot. _iter_hf_cache_snapshots yields
newest first and the main GGUF is resolved in that order, so preserve it
(root still preferred over MTP/ copies) to avoid pairing a fresh main
weight with a stale drafter revision.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-09 06:46:00 -07:00
Daniel Han
d4fbc81d3a
Restore dropped FP8 weight_scale_inv tensors on load (#6978)
* Restore dropped FP8 weight_scale_inv tensors on load

Some block-scale FP8 checkpoints (for example Qwen3.6-27B-FP8, issue #6200) load
with transformers leaving an mlp.gate_proj as a plain bf16 Linear instead of an
fp8 module. Its raw quantized values are read into the bf16 weight and the
weight_scale_inv is dropped as an unexpected key, so the weight is used un-scaled
and the base model is garbage (perplexity around 2 million).

After load, for every checkpoint weight_scale_inv whose live weight is not fp8,
dequantize the orphaned weight in place using the block scale from the checkpoint
index. Modules that were converted correctly keep an fp8 weight and are skipped,
so healthy checkpoints and single-file checkpoints are a no-op.

Verified on Qwen3.6-27B-FP8: 64 gate_proj scales restored, perplexity 2028902 to
8.9. No-op on Qwen3-8B-FP8 (all scales already live).

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Harden FP8 weight_scale_inv restore from review

- Skip restore when the model has no fp8 weights, so an intentionally
  dequantized load (load_in_16bit) is never re-scaled and corrupted.
- Thread revision, subfolder and cache_dir through the index and shard
  downloads so scales come from the same snapshot as the weights.
- Cover unsharded single-file model.safetensors checkpoints (no index).
- Handle transposed block-scale layouts and skip on a true grid mismatch
  instead of applying a wrong scale.
- Match text-only VLM loads where the language_model prefix was stripped.
- Restore on the FastLanguageModel text path too, not only vision.
- Handle a scalar weight_block_size; per-tensor error handling so one bad
  tensor cannot abort the rest or hide a partial mutation.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Address second review round on FP8 scale restore

- Bound peak memory: dequantize block views in place with the fp32 scale
  broadcast instead of materializing a full expanded scale and fp32 copy,
  so a near-VRAM-limit load is not pushed into OOM by the repair.
- Restore on the sequence-classification load path too.
- Cover more VLM key remappings (language_model.model.* to
  model.language_model.*) when matching modules.
- Skip the restore for variant loads (variant=...) rather than risk
  applying default-checkpoint scales to variant weights.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Align FP8 scale restore revision with the loaded weights and warn on disk-offloaded layers

In llama.py the CausalLM/SequenceClassification weight loads resolve model_name on its
default branch (revision is not forwarded there), so read the dropped weight_scale_inv
tensors from the same default branch instead of the requested revision, avoiding rescaling
default-branch weights with scales from another revision.

In loader_utils.py a disk-offloaded layer keeps its weight on the meta device until the
offload hook materializes it, so the scale cannot be applied in place. Skip such layers
explicitly and print a warning rather than silently leaving them unscaled.

* Tighten comments in the FP8 scale restore path

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-09 06:44:44 -07:00
Daniel Han
fb5dc91bb4
Studio: remove dead direct_linux_release_plan path (#7030)
parse_direct_linux_release_bundle and direct_linux_release_plan are no
longer reached by any live code path. Fork Linux installs resolve through
_fork_manifest_release_plans -> _linux_published_attempts, and the upstream
(ggml-org) path uses direct_upstream_release_plan. The dead parser also
called _resolve_linux_bundle_profile, which no longer exists, so its CUDA
branch would raise NameError if ever executed.

Drop both functions and the obsolete TestDirectLinuxNvidiaCpuGate; its live
equivalent TestLinuxPublishedAttemptsNvidiaCpuGate already covers the
NVIDIA no-silent-CPU behaviour.
2026-07-09 05:09:16 -07:00
Daniel Han
b5dca66cb1
scripts: refresh scan_packages allowlist baseline (#7032)
* scripts: refresh scan_packages allowlist baseline

Regenerate scripts/scan_packages_baseline.json against the current
resolved dependency set so the blocking pip scan-packages gate matches
what the scanner now finds. Refreshes evidence hashes for benign
findings whose code shifted lines (unsloth-zoo mlx loader, gguf/mlx
test /tmp fixtures) and adds two mainstream-library entries that were
newly surfaced (torch inductor codecache base64+subprocess compile
cache, torch testing common_utils socket import). Stale entries whose
matching code changed and no longer triggers are dropped.

All entries remain CRITICAL/HIGH findings manually judged benign;
matched on (package, file, check, evidence_hash).

* ci(security-audit): re-run scan when the allowlist baseline changes

The security-audit pull_request trigger listed the scanners but not
their allowlist baselines, so a baseline-only edit never re-ran the
scan that consumes it. A refreshed baseline could therefore merge
without CI confirming its evidence hashes match what the scanner finds.
Add scan_packages_baseline.json and scan_npm_packages_baseline.json to
the paths filter so baseline changes are validated on their own PR.
2026-07-09 04:52:30 -07:00
Daniel Han
534c877d21
Keep native RoPE scaling when extending context; carry rope_theta for linear (#7028)
* Keep native RoPE scaling when extending context; carry rope_theta for linear

When max_seq_length exceeds a model's native window, the loader overwrote the
model's rope_scaling with linear scaling. For models that already ship a scaled
RoPE (llama3/yarn/longrope) that is far worse for long context, and on
transformers v5 the linear dict omitted rope_theta (v5 keeps it under
rope_parameters), so the rotary base fell back to 10000 and broke past ~8K tokens.

Keep the native scaling and just widen the window; only synthesize linear for
plain-RoPE models, and carry rope_theta so v5 keeps the real base.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Only preserve native llama3 when extending context; keep linear fallback otherwise

The patched attention constructor (patch_llama_rope_scaling) rebuilds only linear,
llama3 and longrope and its longrope branch reads a top-level
original_max_position_embeddings, so preserving yarn or a nested-only longrope config
would raise during construction on transformers <= 4.47.1. Keep only llama3 native;
yarn/longrope/other types fall back to the linear override, still carrying rope_theta.

* Correct long-context extension comment to match llama3-only preservation

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-09 04:20:41 -07:00
Daniel Han
cd9d251f15
Fix fast inference crash on compressed-tensors FP8 models (#7025)
* Fix fast_gemv crash on compressed-tensors FP8 models

Loading a compressed-tensors FP8 checkpoint (for example
unsloth/Llama-3.2-1B-Instruct-FP8-Block) with fast_inference=False and
running a forward crashed with 'Parameter object has no attribute absmax'
inside fast_gemv.

A compressed-tensors CompressedLinear exposes an already dequantized bf16
weight at forward time while keeping a weight_scale Parameter. The quant
state resolution in get_lora_parameters/get_lora_parameters_bias fell back
to that weight_scale, so a bf16 weight was routed into the bitsandbytes
fast_gemv/fast_dequantize path, which expects a bitsandbytes QuantState
with an absmax attribute.

Only fall back to weight_scale_inv/weight_scale when the weight is still
fp8. A decompressed bf16 weight then resolves to no quant state and flows
through the normal bf16 path, which already handles bias and the LoRA
backward. Real fp8 and bitsandbytes 4bit weights are unchanged.

* Skip the fast_gemv dispatch test before importing unsloth when bitsandbytes is absent
2026-07-09 04:10:59 -07:00
alkinun
216a1fad33
Fix Windows installer torch index override (#6972)
* Fix Windows installer torch index override

* Clear inherited uv index env vars for pinned installs in studio/setup.ps1 (#6898)

* Harden setup.ps1 index-var clearing to truly remove vars (#6898)

* Apply UV_DEFAULT_INDEX torch index fix to Linux/Mac install.sh (#6898)

* Neutralize all uv index env vars for pinned torch installs (#6898)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: Daniel Han <danielhanchen@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-09 03:46:47 -07:00
oobabooga
3502335120
Studio: add Vulkan llama.cpp support (#5819)
* Studio: add Vulkan llama.cpp support

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Address gemini's feedback

* Studio: move the Vulkan VRAM probe into a standalone script

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Improve Vulkan probe error reporting

* Resolve llama-server symlink so Vulkan build is detected

* Drop unreachable Vulkan fallback in GPU free-memory dispatcher

* Skip the Intel GPU probe when NVIDIA or ROCm is present

* Reserve host RAM headroom for Vulkan integrated GPUs

* Add a `UNSLOTH_FORCE_VULKAN` environment variable

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Honor GGML_VK_VISIBLE_DEVICES, reserve discrete Vulkan VRAM headroom, and clear Intel GPU on --cpu-fallback

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Route Intel and forced-Vulkan hosts to the upstream Vulkan prebuilt, add arm64 Vulkan, keep Vulkan out of RAG auto-detect

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Clear the fork release pin when routing a Vulkan host to the upstream repo

* Gate auto-Vulkan routing on no physical NVIDIA so hidden CUDA devices aren't used

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Pin Vulkan launches with --device Vulkan<i> instead of the raw GGML_VK_VISIBLE_DEVICES index space

* Let user --device override the Vulkan pin, and gate direct Vulkan asset picks on no physical NVIDIA

* Update RAG auto-backend test mocks for the _resolve_auto binary and Vulkan probes

* Keep the add_dll_directory handle alive through the Vulkan probe DLL loads

* Revert RAG auto Vulkan guard, guard multi-backend Vulkan detection, and preserve forced Vulkan across updates

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Use getattr for RTLD_GLOBAL in the Vulkan probe CDLL mode

* Skip CUDA/ROCm APU and datacenter GPU tuning on Vulkan builds

On a Vulkan llama.cpp build gpu_indices are ggml compact ordinals, not
CUDA/ROCm physical ids, so _amd_apu_wants_unified_memory and
_apply_datacenter_env were reading the wrong device. On a mixed AMD APU
plus discrete GPU host that could raise a spurious system-RAM shortfall
and block a valid discrete-GPU load. Gate all three call sites on
not is_vulkan_backend; the Vulkan path already reserves iGPU host
headroom and the backend ignores GGML_CUDA_* anyway.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Tighten Vulkan-guard comment in load_model

* Reduce comments in Vulkan support to be more succinct

* Resolve shell-wrapper llama-server entrypoint to the real lib dir

create_exec_entrypoint falls back to a #!/bin/sh wrapper at the install
root when it cannot symlink into build/bin. _find_llama_server_binary
returns that root entrypoint, but Path.resolve() does not follow a shell
wrapper, so _llama_lib_dir returned the install root and _is_vulkan_backend
missed libggml-vulkan.so -- silently skipping the Vulkan probe and --device
pin on an otherwise valid Vulkan install. Follow the wrapper's exec target
to build/bin. Regression test: test_shell_wrapper_entrypoint_resolves_to_real_lib_dir.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
Co-authored-by: danielhanchen <danielhanchen@gmail.com>
2026-07-09 03:39:48 -07:00
Daniel Han
eb775d3207
Studio /v1/messages: accept thinking and unknown content blocks (#7017)
* Studio /v1/messages: accept thinking and unknown content blocks

The Anthropic-compatible /v1/messages endpoint modeled a message's content as
Union[str, list[{text|image|tool_use|tool_result}]], so any other block type
made Pydantic reject the whole request with
`messages.N.content.str: Input should be a valid string`. Resuming a Claude
session commonly replays assistant turns that carry `thinking` (extended
thinking) blocks, and sometimes a null content for a tool-only turn, both of
which tripped this and returned a 400.

Accept them:
- Add a permissive AnthropicUnknownBlock fallback (any block whose type is not
  one of the four known ones), so thinking/redacted_thinking/provider-specific/
  future blocks validate. A validator keeps known types on their typed models,
  so a malformed known block (e.g. a tool_use without id) still fails cleanly.
- Coerce a null message (and tool_result) content to "" so the converter's
  `for block in content` stays safe.

The converter already drops block types it does not translate, so a thinking
block is not forwarded to the model.

* Studio /v1/messages: keep user content validation strict

Make the thinking/null leniency role-aware so it never silently drops real
user input. Assistant turns (replayed history) still accept unknown/thinking
blocks and coerce a null tool-only turn to empty. User turns keep the strict
boundary: a null user content is rejected, and a content block the converter
cannot translate is rejected instead of being dropped into an empty prompt.

Also remove an empty file committed by accident.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Studio /v1/messages: coalesce resumed user turns and tighten content checks

- The /v1/messages count and generation paths now coalesce the adjacent user
  turns that dropping an empty or null assistant turn can leave behind, so a
  strict GGUF chat template no longer 400s on non-alternating roles.
- A user content block with a non-string type (list / dict) is rejected as a
  clean 400 instead of raising TypeError and escaping as a 500.
- The assistant null-to-empty coercion only applies to an explicit null; an
  assistant turn that omits content entirely still fails required-field
  validation instead of being silently coerced to an empty string.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Studio /v1/messages: tighten comments

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-09 12:20:02 +02:00