* fix: force Unsloth provider selection for opencode
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* opencode: pin the model without clobbering the user's disabled providers
The session overlay wrote disabled_providers unconditionally and the inline
OPENCODE_CONFIG_CONTENT set disabled_providers to an empty list. Since that
inline layer outranks the user's global and project config and opencode
replaces the array rather than merging it, every provider the user had
disabled was silently re-enabled for the session. Only strip 'unsloth' from an
existing disable list, and drop disabled_providers from the inline config.
Also insert --model only on a bare launch: it is a global flag for the TUI, so
placing it before a passthrough subcommand (serve/run) breaks arg parsing; a
subcommand takes the model from the pinned config instead. Parse the printed
OPENCODE_CONFIG_CONTENT with shlex.split in the test so it round-trips under
POSIX shell quoting.
* Re-enable a globally disabled opencode unsloth provider for the session
A fresh OPENCODE_CONFIG overlay omits disabled_providers, and opencode
replaces that array across config layers only when a higher layer sets the
key, so a user's global disabled_providers of ['unsloth', ...] survived the
merge and left the session provider disabled even though the overlay defines
provider.unsloth and pins the model.
Consult the user's global opencode config (XDG_CONFIG_HOME/opencode, or
%APPDATA%/opencode on Windows) when the overlay has no list of its own, and
when the effective list disables unsloth write it back to the overlay minus
unsloth. The provider loads while the user's other disabled providers stay
disabled. Best-effort read: a missing or unparseable global config is a
no-op.
* Override opencode disabled_providers in the inline layer; keep model flag for TUI flags
Re-enabling a disabled unsloth provider now rides in the inline
OPENCODE_CONFIG_CONTENT layer instead of the session overlay. The overlay
sits below a project opencode.json, which could re-disable the provider; the
inline layer outranks both global and project configs and is recomputed each
run, so no-launch reruns never reuse a stale generated list. The effective
disabled list is read from the project config if the repo sets one, else the
global config, across config.json/opencode.json/opencode.jsonc (JSONC
tolerated), and written back minus unsloth only when unsloth is disabled.
Also keep the pinned --model when the opencode passthrough starts with a
top-level TUI flag such as --dir or --continue; only a real subcommand
(serve/run/...) takes the model from config, so a leading '-' now still gets
--model injected.
* Discover the opencode project config by walking up from the cwd
opencode finds a project config by searching ancestor directories, not just
the cwd. Walk from the cwd up to the filesystem root and use the nearest
directory that sets disabled_providers, so running unsloth start opencode
from a subdirectory of a repo whose root config disables unsloth still gets
the inline override.
* Only inject opencode --model on a bare launch; rely on the inline model pin
Injecting --model whenever the passthrough started with a flag could place it
before a subcommand (e.g. opencode --print-logs serve), which opencode can
misparse. --model is unnecessary for any passthrough because the inline
OPENCODE_CONFIG_CONTENT pins the model in the highest-priority layer, so the
session model is forced without the flag. Restrict --model to the bare launch
and pass any other invocation through untouched.
* Register the session provider under a dedicated OpenCode id
Selecting the Unsloth model reliably required the wrapper to re-enable a
user-disabled unsloth provider, which meant reconstructing OpenCode's full
disabled_providers resolution (global, OPENCODE_CONFIG overlay, project config
discovered via --dir or an ancestor walk, .opencode directories,
OPENCODE_CONFIG_DIR, config.json/opencode.json/opencode.jsonc precedence, and
{env:} variable substitution) and overriding it in the inline layer. That is
unbounded and cannot be kept correct.
Register the session provider under a dedicated id (unsloth-studio) instead. A
user's disabled_providers list would never target it, so the session model is
always selectable and the overlay no longer reads or writes disabled_providers
at all: the user's own disables, in whatever config layer, are left exactly as
they are. This removes the JSONC parser, the config-directory scan, and the
ancestor/global resolution helpers, and the tests that exercised them.
* Scope the opencode session to the Studio provider
opencode filters every provider, including a config-defined custom one, through
its enabled_providers allowlist and disabled_providers denylist, and pinning the
model does not bypass that gate (a filtered provider resolves to a not-found
error). The provider arrays are also replaced, not merged, across config layers.
So a user with an enabled_providers allowlist that omits the session provider
would still have the Studio model filtered out.
Set enabled_providers to just the session provider and clear disabled_providers
in the inline OPENCODE_CONFIG_CONTENT overlay (the highest-priority layer, which
replaces these arrays). This guarantees the Studio model loads regardless of the
user's provider filters, without reading or reconstructing their multi-layer
config. It is session-only: the overlay lives in the env for this launch and
never touches the user's config files, so their normal opencode is unchanged and
only this session is limited to the Studio provider.
Also drop the redundant --model on --no-launch so the printed command stays
append-safe for drivers that append a subcommand (the inline pin forces the
model), and parse both POSIX and PowerShell no-launch output in the opencode
tests so they are not shell-specific.
* Pin opencode small_model to the session provider
The session allowlists only the Studio provider, but opencode's separate
small_model (used for lightweight tasks) could still point at another provider
from the user or project config; under the allowlist that provider is filtered,
so the lightweight task would resolve a not-found error mid-session even with the
main model pinned. Pin small_model to the session model in the same inline
overlay so every model use stays on the enabled provider. The session serves one
model, so it is the only valid target, and this stays session-only like the rest
of the overlay.
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
Co-authored-by: Wasim Yousef Said <wasimysdev@gmail.com>