fix: Unsloth Studio support — adapter, onboarding auth-question skip, and broken auth.json credential store - #32
Closed
hwacookie wants to merge 5 commits into
Closed
fix: Unsloth Studio support — adapter, onboarding auth-question skip, and broken auth.json credential store#32hwacookie wants to merge 5 commits into
hwacookie wants to merge 5 commits into
Conversation
added 5 commits
August 20, 2026 00:18
…-compatible servers Pre-existing WIP carried over from before this branch: the generic adapter previously ignored context_window/max_model_len/context_length/ max_context_length and max_completion_tokens fields entirely, always falling back to hardcoded defaults (8192/4096) even when the server reported real values.
Unsloth Studio always requires an API key (no unauthenticated mode at all), so it needs its own adapter rather than falling through to the generic OpenAI-compatible fallback. - fingerprint() identifies the backend from the `Server: unsloth-studio` response header, present on every response (401 with no/invalid key, and 200 authenticated alike) — verified against a live instance via curl. Deliberately does NOT claim a bare 200 + data[] on its own (indistinguishable from any other OpenAI-compatible server); falls through to the generic adapter in that case, per the existing fingerprint-discrimination-must-be-honest rule. - Adds IntrospectLoaded support from the real `loaded: boolean` field each /v1/models entry reports. BREAKING (contract): adds an optional `authRequired?: boolean` field to `BackendAdapter` (src/core/backend-adapter.ts) and a new "unsloth" BackendKind (src/core/capability.ts). Both files are the FROZEN CONTRACT per ARCHITECTURE.md — this bumps CONTRACT_VERSION 2 -> 3. The change itself is additive/non-breaking for existing adapters, but the frozen files were touched and need explicit review. Fixture/tests use response data captured verbatim from a live Unsloth Studio instance, not synthesized examples.
… backends (Problem 2 of Hypabolic#31) The manual-add flow asked the auth question before ever probing the server, so a user could pick "No authentication" for a backend that can never work without a key (e.g. Unsloth Studio) and land on the generic "could not identify the server" dead end once every adapter's fingerprint 401s. Now pre-probes unauthenticated, once, before asking. If a matching adapter declares `authRequired: true`, the auth-choice select is skipped entirely and the user goes straight to the API-key prompt, with an explicit notify naming which backend needs it. Falls through to the original behaviour unchanged when the pre-probe fails or matches nothing, or when no adapter declares authRequired.
…blem 3 of Hypabolic#31) `createPiCredentialStore()` built the CredentialStore from `ctx.modelRegistry.authStorage`, which pi-coding-agent >=0.80.8 removed from the extension-facing SDK surface entirely (see that release's CHANGELOG: "AuthStorage and its storage backends are no longer exported"). Since Crossbar's package.json pins the devDependency to 0.79.9 but the globally-installed `pi` binary users actually run can be (and was, in the field) much newer, this crashed with "Cannot read properties of undefined (reading 'set')" the moment a server with `auth: apiKey` was added. Replaces it with auth-json-credential-store.ts, which reads/writes auth.json directly in Pi's own flat shape (atomic writes, 0600, read-modify-write so it never touches another provider's entry) — restoring the "secrets live only in auth.json" guarantee without depending on the removed SDK internals. Its header doc also explains why built-in providers (anthropic, openai, github-copilot, ...) were never affected: they run through Pi's internal ModelRuntime, which kept privileged access all along; only the EXTENSION provider API lost its bridge.
…sentinel Found while manually testing the Hypabolic#31 fixes end-to-end against a real Unsloth Studio instance: adding a keyed server succeeded (key persisted via the new auth-json-credential-store), but `pi.setModel()` still silently returned false ("Pi could not select <model>"). registerServer() registers the provider with apiKey: "$ENVVAR" (never the plaintext key, by design), relying on Pi's own config-value resolver to fill it in from process.env at request time — but nothing ever set that env var. The original assumption (encoded in a now- corrected comment) was that Pi resolved auth.json by provider id on its own for ANY registered model; empirically true for pi-coding-agent 0.79.9 (verified via the real AuthStorage/ModelRegistry in provider-shim.test.ts) but not reliably observable for extension- registered providers under newer Pi versions, whether due to the extension-facing API changes in >=0.80.8 or an in-memory credential snapshot that never sees our out-of-band auth.json write. registerServer() and preloadCachedProviders() now set process.env[envVarFor(record.id)] to the resolved key immediately before pi.registerProvider(...), so Pi's live process.env read finds it regardless. Added an end-to-end CLI integration test that spawns the real local `pi` binary against an isolated agent dir with a throwaway dummy key and confirms the model is listed as available via --list-models — the exact chain that was broken. Also cleans up test pollution: registerServer's process.env mutation is real and process-global, so provider-shim.test.ts now resets it in afterEach to keep the (deliberately isolated) real-ModelRegistry "keyed availability" tests hermetic.
Contributor
Author
|
Superseded by #33 (same commits, but from a topic branch instead of my fork's main). Closing this one. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #31.
Summary
Three separate problems reported when adding an Unsloth Studio server (see #31 for full repro/root-cause writeups), plus one more found while manually verifying the fix end-to-end against a live instance.
1. New Unsloth Studio adapter (
src/adapters/unsloth.ts)Unsloth Studio has no unauthenticated mode at all — every request, including
GET /v1/models, requires a bearer key.fingerprint()identifies it from theServer: unsloth-studioresponse header, present on every response (401 with no/invalid key, and 200 authenticated alike) — verified against a live instance via curl, not guessed. Deliberately does not claim a bare200 + data[]on its own (indistinguishable from any other OpenAI-compatible server) — falls through to the generic adapter in that case. AddsIntrospectLoadedsupport from the realloaded: booleanfield each model entry reports.2. Onboarding dialog now skips "No authentication" for
authRequiredbackends (src/ui/onboarding.ts)The manual-add flow asked the auth question before ever probing the server, so a user could pick "No authentication" for a backend that can never work without a key and land on a generic "could not identify the server" dead end. Now pre-probes unauthenticated once before asking; if a matching adapter declares
authRequired: true, the choice is skipped entirely and the user goes straight to the API-key prompt.3. Broken credential store on pi-coding-agent >=0.80.8 (
src/registry/auth-json-credential-store.ts)createPiCredentialStore()readctx.modelRegistry.authStorage, which pi-coding-agent >=0.80.8 removed from the extension-facing SDK entirely ("AuthStorage and its storage backends are no longer exported", per that release's CHANGELOG). Crossbar'spackage.jsonpins the devDependency to0.79.9, but the globally-installedpibinary users actually run can be much newer — this crashed withCannot read properties of undefined (reading 'set')the moment a keyed server was added. Replaced with a module that reads/writesauth.jsondirectly, in Pi's own flat shape (atomic writes, 0600, read-modify-write so it never touches another provider's entry).4.
process.envbridge for the$ENVsentinel (found during live testing)Even with the key correctly persisted,
pi.setModel()still silently returnedfalse.registerServer()registers providers withapiKey: "$ENVVAR"(never the plaintext key), relying on Pi's own config-value resolver to fill it in fromprocess.env— but nothing ever set that variable.registerServer()andpreloadCachedProviders()now bridge the resolved key intoprocess.env[envVarFor(record.id)]immediately beforepi.registerProvider(...).Testing
tsc --noEmit: clean.vitest run: 668 passed, 8 skipped, 0 failed.tests/integration/cli-preload.test.ts) spawns the real localpibinary against an isolated agent dir with a throwaway dummy key and confirms the model is listed as available via--list-models— the exact chain that was broken (Problem 4).unsloth.fixture.tsuses response data captured verbatim from a live Unsloth Studio instance, not synthesized examples.Commits
f206cc7fix(generic): read context window/maxTokens fields reported by OpenAI-compatible servers (pre-existing WIP carried along)c4a7c3bfeat(adapters): add Unsloth Studio adapter (Problem 1)1db85ebfix(onboarding): skip the "No authentication" choice for authRequired backends (Problem 2)88bb28afix(registry): replace broken authStorage-based credential store (Problem 3)4b0d8b3fix(shim): bridge the resolved API key into process.env for the $ENV sentinel (Problem 4)