Provider extensions
A Python extension can register an LLM provider. The router selects it in the
same way as built-in providers. This page describes vis.Provider, callbacks
and managed providers. To add a provider without an extension, see
Configuration.
Before you start
Use a provider extension when authentication or model discovery needs Python code. For a fixed endpoint and API key, prefer provider configuration. The example needs no external Python dependencies, but its endpoint and model names are placeholders: replace them with a service you are authorized to use.
Declare and load a provider
- Save this complete entry as
.vis/extensions/example_provider.pyin your project. It readsEXAMPLE_API_KEYonly when a callback runs, not while declaring the provider.
import os
import blockether.vis.extension as vis
def credential() -> vis.ProviderCredential | None:
token = os.environ.get("EXAMPLE_API_KEY")
return vis.ProviderCredential(token) if token else None
def status() -> vis.ProviderStatus:
return vis.ProviderStatus(is_authenticated=bool(os.environ.get("EXAMPLE_API_KEY")),
source="env-var")
vis.register(vis.Extension(
name="provider-example",
description="An OpenAI-compatible provider.",
env=["EXAMPLE_API_KEY"],
providers=[vis.Provider(
id="example",
label="Example AI",
preset=vis.ProviderPreset(
base_url="https://gateway.example.com/v1",
api_style="openai",
default_models=["example-model"],
),
get_token_fn=credential,
status_fn=status,
)],
))
- Supply
EXAMPLE_API_KEYthrough the project's environment configuration, a local.envfile or the gateway's startup environment. Do not commit the value. See environment resolution. - Start Vis in that project or run
/reload, then inspectvis-agent providers status examplein the terminal. The status callback reports whether a credential is present; it does not verify it with the service. - Add
examplethrough Add provider orprovidersconfiguration, select the real model name and make a request to verify the connection.
The preset supplies endpoint and model defaults; callbacks supply credentials and status. Registration or a positive local status is not proof that the service accepts the key. No interactive login is defined in this minimal example.
Records
| Record | Fields |
|---|---|
ProviderPreset |
base_url, api_style, default_models, responses_path, llm_headers, extra_body, is_hidden, JSON-only extra |
ProviderCredential |
token, optional api_url, api_style, responses_path, llm_headers, source |
ProviderStatus |
is_authenticated, source, display-only extra |
ProviderModel |
name, context, is_tool_call, is_image_input, JSON-only extra |
ProviderLimits |
limits (list of ProviderLimit), rpm, tpm, note, error |
api_style accepts the same values as configuration: anthropic, openai,
openai-responses, gemini and their aliases. Configuration overrides the
credential, which overrides the preset. A credential can therefore supply an
endpoint discovered during authentication while still allowing a user override.
Header maps replace the entire field at each level.
Token and header fields are excluded from a record's repr. Never log
credentials or put them in status metadata. A missing credential is None,
not a record with an empty token.
Callbacks
| Callback | Signature | Purpose |
|---|---|---|
get_token_fn, detect_fn |
() -> ProviderCredential | None |
passive credential read; never starts a login |
refresh_token_fn |
(rejected: str | None) -> ProviderCredential | None |
renew a token; a zero-argument form is accepted |
status_fn |
() -> ProviderStatus | None |
connection state, not quota |
limits_fn |
() -> ProviderLimits | None |
usage report |
auth_fn |
(printer) -> str | bool | None |
interactive login; print instructions with printer(line) |
auth_prompt_fn |
() -> Sequence[str] | str | None |
static login guidance |
logout_fn |
() -> None |
discard the credential |
enrich_models_fn |
(provider, router_opts) -> Sequence[ProviderModel] | None |
extend the model list; None keeps defaults |
on_selected_fn |
(event) -> None |
notification after selection |
Callbacks are optional and synchronous. Async functions and invalid signatures are rejected at declaration. Passive callback errors are logged and return no result; authentication errors are returned to the caller.
Startup, status probes and limits polling run without a session. vis.shell
and vis.jailed_shell work there; vis.ask and vis.jailed_shell_session do
not.
Report usage with the limits contract:
def limits() -> vis.ProviderLimits:
return vis.ProviderLimits(limits=[
vis.ProviderLimit("daily-tokens", "Daily tokens",
scope="account", kind="tokens",
precision="exact", source="provider-api",
used=25.49, limit=100,
window=vis.ProviderLimitWindow("calendar", unit="day", size=1)),
])
ProviderLimit also accepts remaining, is_unlimited, subject and
note. Inspect the result with vis-agent providers limits <id>.
Managed providers
A managed provider is registered when its extension loads. The extension
defines its endpoint and models and stores credentials outside state.yml.
Set is_managed=True:
vis.Provider(
id="corp-gateway",
label="Corp Gateway",
is_managed=True,
preset=vis.ProviderPreset(base_url="https://gateway.example.com/v1",
api_style="openai",
default_models=["corp-large", "corp-small"]),
get_token_fn=_issued_token,
auth_fn=_browser_oauth, # optional: provider-owned first-use login
)
| Ordinary provider | Managed | |
|---|---|---|
| Owner | user configuration | the extension |
| Add provider dialog | listed | never listed |
| Appears | after configuration | when the extension loads |
| Authentication | configured flow | runtime credential, or the extension's auth_fn |
To implement one:
- Set
is_managed=Trueand supply apresetwithdefault_models. - Make
get_token_fn()a passive read that returnsProviderCredential(token)orNonewhile signed out. - When sign-in is needed, supply
auth_fn(printer). It completes the interactive flow, stores the credential whereget_token_fn()reads it, and returns"ok"or"already-authenticated". - Keep
status_fn()andlimits_fn()passive. Put renewal inrefresh_token_fn().
With auth_fn, a request that finds no usable token runs the login once,
calls get_token_fn() again and resumes; concurrent requests share the flow.
Status probes, startup and opening the model picker never run auth_fn. Test
the explicit path with vis-agent providers auth <id>, then make a request
while signed out to verify first-use login.
See also
- Configuration — providers declared in
vis.ymland the router. - Extending Vis — writing the provider's extension.
- Distributions — including a provider in a custom build.
