Skip to content

Tau AI SDK Integration

Tau delegates LLM provider protocol handling to github.com/samcharles93/ai-sdk, a Go library modelled on the Vercel AI SDK. This document describes the current integration: how providers are resolved, how models are discovered, and how the streamer works.

Architecture

internal/providers/          ← tau's provider catalogue and auth state
    catalog.go               ← well-known providers: IDs, base URLs, auth kind
    state.go                 ← ~/.config/tau/auth.yaml (enabled/disabled/OAuth)
    resolve.go               ← merges config + state + env → usable provider set
    effective.go             ← Effective() entry point

internal/providers/snapshot/
    snapshot.go              ← //go:embed models.json; Catalog() → runtime.Catalog
    models.json              ← offline curated catalogue (11 providers, ~427 models)
    gen/main.go              ← regenerate from models.dev (go generate)

internal/app/
    chat.go                  ← newRuntimeForProviders(), aggregateModelRefs()
    provider_runtime.go      ← live-reloadable runtime wrapper (providerRuntime)
    streamer.go              ← Streamer / NewDynamicStreamer - agent.Streamer impl
    live_models.go           ← liveModelRefs() for dynamic providers (Ollama)

github.com/samcharles93/ai-sdk (external)
    pkg/runtime              ← Runtime, Catalog, ProviderConfig, ModelInfo
    pkg/chat                 ← Provider, Request, Response interfaces
    pkg/provider/openai      ← OpenAI-compatible HTTP client (default)
    pkg/provider/anthropic   ← Native Anthropic Messages API client

The coordinator, event bus, tool registry, and session store are all tau-internal - ai-sdk only handles the raw provider streaming protocol.

Provider Catalogue

internal/providers/catalog.go contains the built-in list of well-known providers. Each CatalogEntry carries:

FieldPurpose
IDtau's canonical name (e.g. "deepseek")
DisplayNameHuman-readable label shown in /provider menus
BaseURLDefault API endpoint
EnvVarsEnvironment variables to probe for the API key (first set wins)
AuthAuthAPIKey, AuthOAuth, or AuthNone
OAuthHandlerProvider-specific managed login/refresh handler
HeadersStatic request headers merged into provider calls
Classai-sdk runtime class; empty → "openai-compatible"
CatalogIDmodels.dev key when it differs from tau's ID (e.g. Gemini → "google")
LiveModelsWhen true, model list is fetched from /v1/models at runtime

Built-in providers

tau IDDisplay nameAuthNotes
openaiOpenAIAPI key (OPENAI_API_KEY)
anthropicAnthropic (Claude)API key (ANTHROPIC_API_KEY)Native Messages API, class "anthropic"
deepseekDeepSeekAPI key (DEEPSEEK_API_KEY)
openrouterOpenRouterAPI key (OPENROUTER_API_KEY)
geminiGoogle GeminiAPI key (GEMINI_API_KEY)models.dev key "google"
groqGroqAPI key (GROQ_API_KEY)
mistralMistralAPI key (MISTRAL_API_KEY)
togetherTogether AIAPI key (TOGETHER_API_KEY)
xaixAI (Grok)API key (XAI_API_KEY)
cerebrasCerebrasAPI key (CEREBRAS_API_KEY)
minimaxMiniMaxAPI key (MINIMAX_API_KEY)
ollamaOllama (local)NoneLive model discovery from localhost:11434
ollama-cloudOllama (cloud)API key (OLLAMA_API_KEY)
github-copilotGitHub CopilotOAuthDevice-code login; Copilot token exchange supplies token/base URL/account model IDs
openai-codexOpenAI CodexOAuthDevice-code login; ChatGPT backend Responses transport and live Codex model discovery

Providers are activated either by the user's hand-written config.yaml, by the managed auth.yaml (via /provider), or by auto-detecting a set API key in the environment.

Model Catalogue (Embedded Snapshot)

Interactive mode (RunChat) and the TUI's /model picker load models from an embedded offline snapshot (internal/providers/snapshot/models.json). This means:

  • No network request is needed at startup.
  • Only models with tool_call: true are included (tau requires tool calling).
  • The snapshot is curated to tau's built-in provider set.

The snapshot is generated by:

bash
go generate ./internal/providers/snapshot/...
# or directly:
go run ./internal/providers/snapshot/gen/main.go \
    -output internal/providers/snapshot/models.json

The generator fetches models.dev, filters to tau providers and tool-capable models, then writes deterministic JSON. Commit the updated models.json after running the generator.

tau models / tau refresh subcommands still use the network catalog (models.dev) and are independent of the embedded snapshot. They are useful for exploring what a provider offers, but interactive mode ignores them.

Model Discovery Flow (Interactive Mode)

  1. providers.Effective() → merged []ProviderConfig from config + auth state + env.
  2. newRuntimeForProviders(provs) → builds runtime.Runtime loaded with the embedded snapshot.Catalog().
  3. aggregateModelRefs(ctx, rt, insecure, provs) iterates providers:
    • openai-codexcodexModelRefs() → ChatGPT backend Codex models endpoint
    • LiveModels: true (Ollama local) → liveModelRefs() → GET /v1/models
    • others → rt.Models(providerID) from snapshot, filtered by toolCapable()
    • every ChatModelRef carries Provider: providerID so the UI can route correctly.
  4. pickModel(...) selects the model from --model flag or default_model config; returns a zero ref (empty ID) if neither is set - the session starts unselected and the user chooses with /model.

Dynamic Streamer

A single Streamer serves all providers for the lifetime of a session. It resolves the provider per turn using a providerResolver closure:

go
// On each turn, reads the session's current provider + model
ref := session.Provider.Name + "/" + session.Model.ID
provider, modelID, err := providerRuntime.runtime().ChatProvider(ctx, ref)

This means switching model or provider (via /model or /provider) takes effect on the next turn without rebuilding the coordinator.

providerRuntime.reload(ctx) is called after /provider to rebuild the underlying runtime with the updated provider set, so newly enabled providers are immediately available.

URL Normalisation Rule

ai-sdk's openai client applies the following rule to base URLs:

  • URL with a path (e.g. https://api.deepseek.com/v1) → used as-is.
  • Host-only URL (e.g. https://api.anthropic.com) → /v1 is appended.
  • Endpoint path is /chat/completions (no /v1 prefix). Final URL: baseURL + "/chat/completions".

Common mistake: a base_url that already contains /v1 must be left as-is. Adding /v1 again creates double-path URLs like /v1/v1/chat/completions and causes 404 errors.

All built-in catalog entries already use the correct form. Only hand-written provider configs in config.yaml can hit this issue.

Provider Classes

ClassUsed forProtocol
openai-compatible (default)All standard providersOpenAI Chat Completions API over HTTPS
anthropicAnthropic onlyNative Messages API; x-api-key header; base URL must be host-only

resolveProviderClass() in internal/app/chat.go maps the provider's Type field (from config.yaml) to a class. Unknown types fall through to "openai-compatible". The class for Anthropic is set in the built-in catalog entry; hand-written Anthropic configs must set type: anthropic.

The provider catalogue and embedded model snapshot supply defaults for every well-known provider. A minimal config only needs auth:

yaml
# ~/.config/tau/config.yaml

providers:
  - name: deepseek
    base_url: https://api.deepseek.com/v1
    auth:
      type: api_key
      api_key_env: DEEPSEEK_API_KEY

default_provider: deepseek
default_model: deepseek-chat

Alternatively, set DEEPSEEK_API_KEY in your environment and run tau without any config - the auto-detection path will pick it up.

Model metadata (context window, pricing, reasoning capabilities) comes from the embedded snapshot; you do not need to repeat it in config.yaml unless you are overriding a specific value.

Enabling Providers Without config.yaml

Tau auto-detects API keys from the environment. Exporting any of the env vars listed in the provider catalogue above is enough to enable that provider. The TUI's /provider command toggles a provider on and persists the key to ~/.config/tau/auth.yaml.

Adding a New Provider

  1. Add a CatalogEntry to the catalog slice in internal/providers/catalog.go.
  2. Set Class if the provider is not OpenAI-compatible.
  3. Set CatalogID if models.dev uses a different key than tau's ID.
  4. Set LiveModels: true if the model list is dynamic (local servers).
  5. Run go generate ./internal/providers/snapshot/... to update models.json.
  6. Update tests in internal/providers/providers_test.go.

Reasoning / Effort

Models that support adjustable reasoning effort advertise reasoning_options in the models.dev catalog. The snapshot generator carries these through to ModelInfo.ReasoningOptions. At runtime:

  • modelInfoToModelConfig() extracts effort levels into ModelConfig.ReasoningEfforts.
  • The TUI's /effort command offers only the levels the model advertises.
  • Streamer.buildRequest() maps tau effort levels to OpenAI API values (low, medium, high, maxxhigh) via effortToOpenAI().

Built with VitePress.