feat(provider): connect a local endpoint that needs no authentication - #56
Merged
Merged
Conversation
A model served on a plain OpenAI-compatible endpoint — Ollama, llama.cpp, vLLM, LM Studio — needs no credential, and the runtime refused to connect one at three independent layers: `provider add` demanded an API key, the connection test reported `Provider API key is not configured`, and a turn failed in `LocalModelResolver` with `api_key not configured`. A key is now optional everywhere a provider is created, saved, tested or resolved. Nothing infers "no authentication" from a URL and no new vocabulary was introduced: an entry that carries no key simply declares no credential scheme, and every request to it is sent without one. - `packages/shared/src/credential-headers.ts` (new) names the credential headers the three protocols use, clears them, and owns the placeholder key the vendored transport requires (`openai-completions` throws on a falsy key). - Resolution: the BYOK plan and the managed path resolve a keyless endpoint to that placeholder plus `unauthenticatedEndpoint`, which `composeStreamFn` turns into cleared credential headers on the request's own options — the last header source pi-ai merges, and the one its SDKs read a `null` from as "remove this default header". A credential the connection declares itself survives. - Auxiliary calls stay credential-free too: the remote token counter skips its probe for such an endpoint (its requests are credential-shaped and it estimates locally instead), and the connectivity paths (`buildProviderHeaders`, `buildModelDiscoveryHeaders`, the connection test, model discovery) omit the credential header when no key is configured. - Management: a candidate or a created provider without a key is stored with no `apiKey` and no `authMode`, and an empty key clears a saved one instead of being rejected as invalid. A keyless connection is selectable; the connection test is the gate, as it already is for a failed test. - CLI: `provider add` no longer requires a key or `--api-key-env`, and `provider list` reports `no key sent` rather than `no key`. - TUI: the catalogue gains a **Local model** entry that pre-fills an OpenAI-compatible endpoint (editable, Ollama's default port) and skips the protocol step; the API Key step is labelled optional and an empty value connects; the `/provider` editor tests and saves a connection with no key and clears a saved one when its API Key field is submitted empty. Coverage: a new agent-core case set pins the header clearing, the resolver and plan cases pin the keyless plan and the placeholder, the counter case pins the local estimate, the management cases pin the saved shape and the selectable connection, the TUI cases pin the entry, the optional key step and the cleared key, and `test:byok` runs the whole path against a loopback stand-in — `provider add`, `provider list`, the connection test and a turn — asserting that no request carries `Authorization` or `x-api-key`.
The verification record carries the code revision, the full gate list with counts, the 16 tests the change adds, the end-to-end BYOK run and the PTY session, and its NOT RUN list. The provider documentation now describes the keyless case where it already described a local server: README and docs/examples.md state the lookup rule for the key variable instead of promising that no credential is ever sent when the flag is omitted.
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.
What
A model served on a plain OpenAI-compatible endpoint — Ollama, llama.cpp, vLLM, LM Studio — needs no credential, and
kcoderefused to connect one at three independent layers:kcode provider addfailed withProvider API key is missing. Set MCODE_PROVIDER_API_KEY or pass --api-key-env <name>.kcode provider testansweredProvider API key is not configured.LocalModelResolver: api_key not configured for provider "custom_provider:…".The key is now optional everywhere a provider is created, saved, tested or resolved. Nothing infers "no authentication" from a URL, and no new vocabulary was introduced: an entry that carries no key simply declares no credential scheme, and no request to it is sent with one.
How
packages/shared/src/credential-headers.ts(new) names the credential headers the three protocols use, clears them, and owns the placeholder key the vendored transport requires (openai-completionsthrows on a falsy key, sopackages/agent-corehands it one that must never reach the wire).unauthenticatedEndpoint;composeStreamFnturns that into cleared credential headers on the request's own options — the last header source pi-ai merges, and the one its SDKs read anullfrom as "remove this default header". A credential the connection declares itself survives.buildProviderHeadersandbuildModelDiscoveryHeadersomit the credential header when no key is configured, and the connection test's redaction list tolerates an absent key.apiKeyand noauthMode; an empty key clears a saved one instead of being rejected as invalid. Such a connection is selectable — the connection test is the gate, as it already is for a connection whose test failed.provider addno longer requires a key or--api-key-env(naming a variable that is empty is still an error: the key was asked for and not found).provider listreportsno key sentrather thanno key./providereditor tests and saves a connection without a key and clears a saved one when its API Key field is submitted empty.Evidence
test:byokruns the whole path against a loopback stand-in —provider add,provider list, the connection test and a real turn — and asserts that no request carriesAuthorizationorx-api-key, including the token-counter probe the first run caught.A PTY run of the built CLI against an isolated data directory and the same kind of stand-in drove the new catalogue entry end to end: the row renders as
Local model OpenAI-compatible server · API key optional, the flow reachesAPI Key (optional)withOptional: a server that needs no authentication is connected with the field left empty, an empty key connects (Provider added: Local model), and the stand-in logged one request —/v1/chat/completions— withauthorization: nullandx-api-key: null.Unit coverage: 7 cases in
packages/agent-core/test/unit/pi-turn-runner/unauthenticated-endpoint.test.tspin the header clearing and the declared-header exception; the resolver, plan, counter, management, onboarding and editor suites pin the keyless plan, the placeholder, the local estimate, the saved shape, the selectable connection, the new catalogue entry, the optional key step and the cleared key.pnpm verifyis green — the full gate list and its counts are in thedocs/verification.mdentry this PR adds.Limits
packages/local-runtime/src/runtime/host.tsand its own model resolver) still requires a key; the product drives the v2 runtime, and only its token counter is shared with v1 (that one is handled here).401at connection-test time rather than a local "api key not configured" error. That is the deliberate cost of "the key is optional everywhere": the runtime does not guess which endpoints require one.