Providers & Auth
This page is the practical provider tutorial for Go developers embedding Seshat. Instead of listing knobs first, it starts from the real integration scenarios: local operator apps, multi-user backends, mixed provider stacks, and Codex-specific auth flows.
Public SDK Auth Contract
The exported Go SDK keeps the auth boundary intentionally simple. Your host app can pass credentials directly through ClientConfig.APIKey, use provider-specific values in ProviderConfig, or resolve credentials dynamically with CredentialResolver.
OAuth login is now exposed for Go hosts through sdk.NewOAuthDeviceFlow. That means a desktop app, CLI app, or internal operator tool can initiate login itself and then feed the resulting resolver back into the same runtime without inventing a separate auth bridge.
Choose The Right Scenario First
Best for a CLI app, a local desktop utility, or an internal operator tool where one human controls one runtime and simple env-var auth is acceptable.
Best for SaaS or internal platforms where credentials belong to each tenant or user and must be resolved dynamically per request.
Best when reasoning, image generation, speech, and transcription should not all come from the same provider.
Best when you want ChatGPT-backed Codex access inside your own app, including device flow login and downstream gRPC handoff.
Scenario 1: Single-User CLI App, Desktop Utility, Or Operator Tool
This is the simplest embedding path. Pick a model, inject one API key, and optionally override transport details through ProviderConfig. If your host product also uses the Seshat auth store, you can bootstrap login with the CLI and then let the runtime reuse the stored credentials.
client, err := sdk.NewClient(&sdk.ClientConfig{
Model: sdk.ModelIdentifier{
Provider: sdk.APIProviderAnthropic,
Model: "claude-sonnet-4-20250514",
},
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
ProviderConfig: &providers.Config{
Provider: types.APIProviderAnthropic,
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
BaseURL: "https://api.anthropic.com",
},
})seshat login --provider openaiScenario 2: Multi-User Backend Or SaaS Integration
In a backend, process-wide environment variables are usually the wrong boundary. The cleaner pattern is to let your app own secret storage, token exchange, or tenant settings, then resolve the provider credential at runtime through CredentialResolver.
| Provider | Auth flow | Environment variables | Config path | Notes |
|---|---|---|---|---|
| anthropic | API key | ANTHROPIC_API_KEY | ClientConfig.APIKey or ProviderConfig.APIKey | Direct Claude path. Provider config also supports alias mapping and routing setup. |
| openai | API key | OPENAI_API_KEY | ClientConfig.APIKey or ProviderConfig.APIKey | Also reused by current image, TTS, and STT capability providers when configured. |
| ollama | Usually none | OLLAMA_HOST, optional OLLAMA_API_KEY | ProviderConfig.BaseURL or detected local host | Local daemon path. Runtime discovery checks OLLAMA_HOST and localhost defaults. |
| bedrock | AWS credentials | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION | ProviderConfig.Region | Validation explicitly requires AWS_REGION. Transport derives the rest from AWS auth state. |
| vertex | GCP project + region | ANTHROPIC_VERTEX_PROJECT_ID, CLOUD_ML_REGION | ProviderConfig.ProjectID, ProviderConfig.Region | Claude-on-GCP path. Validation fails until both project id and region are present. |
| foundry | API key + Azure resource | ANTHROPIC_FOUNDRY_API_KEY, ANTHROPIC_FOUNDRY_BASE_URL, ANTHROPIC_FOUNDRY_RESOURCE | ProviderConfig.APIKey, BaseURL, Region | Uses Azure-style api-key auth plus the Foundry resource header when configured. |
| gemini | API key | GOOGLE_API_KEY for auth resolution; GOOGLE_GEMINI_API_KEY appears in validation messages | ClientConfig.APIKey or ProviderConfig.APIKey | There is a current naming mismatch in code between auth resolution and validation messaging, worth normalizing later. |
| z-ai | x-api-key | Z_AI_API_KEY | ProviderConfig.APIKey | OpenAI-compatible body format, but auth headers are provider-specific. |
| openrouter | API key | OPENROUTER_API_KEY | ProviderConfig.APIKey | Useful as a one-key routing layer across many vendors. |
| minimax | API key | MINIMAX_API_KEY | ProviderConfig.APIKey | Default config maps MiniMax aliases and long-output models. |
| workers-ai | API key | CLOUDFLARE_API_KEY | ProviderConfig.APIKey | Model ids are route-style Cloudflare identifiers. |
| mistral | API key | MISTRAL_API_KEY | ProviderConfig.APIKey | OpenAI-compatible transport with Mistral-specific base URL. |
| codex | OAuth device flow or CODEX_ACCESS_TOKEN / CODEX_API_KEY override | CODEX_ACCESS_TOKEN, CODEX_API_KEY, optional OPENAI_CLIENT_ID, optional SESHAT_AUTH_PATH | OAuth token persisted in ~/.seshat/auth.json or explicit ProviderConfig.APIKey | Special login path. Runtime can resolve refreshed OAuth tokens from the local auth store or reuse an explicit bearer token. |
| deepseek | API key | DEEPSEEK_API_KEY | ProviderConfig.APIKey | OpenAI-compatible direct provider with chat/reasoner aliases. |
| opencode | API key | OPENCODE_API_KEY | ProviderConfig.APIKey | Gateway provider exposing curated Claude, Codex, and GLM options. |
| kimi | API key | No dedicated env mapping in providerEnvVar today | Would require explicit ProviderConfig/API key wiring | Provider registry support exists, but the default config/auth wiring is still incomplete compared to the others. |
type VaultResolver struct {
Keys map[string]string
}
func (r VaultResolver) ResolveAPIKey(ctx context.Context, provider string) (string, error) {
if key, ok := r.Keys[provider]; ok {
return key, nil
}
return "", nil
}
client, err := sdk.NewClient(&sdk.ClientConfig{
Model: sdk.ModelIdentifier{
Provider: sdk.APIProviderOpenAI,
Model: "gpt-5.5",
},
CredentialResolver: VaultResolver{
Keys: map[string]string{
"openai": os.Getenv("OPENAI_API_KEY"),
"codex": os.Getenv("CODEX_ACCESS_TOKEN"),
},
},
})The current code still has a naming mismatch around Gemini: auth resolution uses GOOGLE_API_KEY, while some validation messaging still mentions GOOGLE_GEMINI_API_KEY. The runtime works, but the naming should be normalized later.
Scenario 3: One Reasoning Model, Different Capability Providers
Seshat does not require one provider to own everything. The main reasoning model can come from one vendor while image generation, speech-to-text, and text-to-speech use completely different providers. This is the right shape when you want better cost control, better local quality on one modality, or a more sovereign deployment mix.
| Capability | Current provider options | SDK config path | Notes |
|---|---|---|---|
| Core reasoning | Anthropic, OpenAI, Ollama, Bedrock, Vertex, Foundry, Gemini, Z.ai, OpenRouter, MiniMax, Workers AI, Mistral, Codex, DeepSeek, OpenCode, Kimi | ClientConfig.Model + ProviderConfig | This is the main session model path used by the mono-run loop. |
| Image generation | OpenAI, Gemini | ClientConfig.ImageGeneration | Current TUI capability picker exposes OpenAI and Gemini for generate_image. |
| Text to speech | OpenAI | ClientConfig.TextToSpeech | Current runtime wiring only initialises OpenAI for text_to_speech. |
| Speech to text | OpenAI | ClientConfig.SpeechToText | Current runtime wiring only initialises OpenAI for speech_to_text. |
client, err := sdk.NewClient(&sdk.ClientConfig{
Model: sdk.ModelIdentifier{
Provider: sdk.APIProviderAnthropic,
Model: "claude-sonnet-4-20250514",
},
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
ImageGeneration: &sdk.ImageGenerationConfig{
Provider: "openai",
Model: "gpt-image-1",
APIKey: os.Getenv("OPENAI_API_KEY"),
},
TextToSpeech: &sdk.TextToSpeechConfig{
Provider: "openai",
Model: "gpt-4o-mini-tts",
Voice: "alloy",
APIKey: os.Getenv("OPENAI_API_KEY"),
},
SpeechToText: &sdk.SpeechToTextConfig{
Provider: "openai",
Model: "gpt-4o-transcribe",
APIKey: os.Getenv("OPENAI_API_KEY"),
},
})Scenario 4: Codex Inside Your App Or Through gRPC
Codex is the one provider that needs extra explanation. It uses a ChatGPT-backed transport, a dedicated base URL, and an auth story that is different from the standard API-key-only path. For local products, the most ergonomic path is to launch the device flow directly from the SDK and then reuse the returned resolver.
flow, err := sdk.NewOAuthDeviceFlow(sdk.OAuthDeviceFlowConfig{
Provider: string(sdk.APIProviderCodex),
Persist: true,
})
if err != nil {
log.Fatal(err)
}
challenge, err := flow.Start(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Open %s and enter %s\n", challenge.VerificationURL, challenge.UserCode)
token, err := flow.Wait(ctx, challenge)
if err != nil {
log.Fatal(err)
}
client, err := sdk.NewClient(&sdk.ClientConfig{
Model: sdk.ModelIdentifier{
Provider: sdk.APIProviderCodex,
Model: "gpt-5.3-codex",
},
CredentialResolver: flow.CredentialResolver(),
})
if err != nil {
log.Fatal(err)
}
_ = tokenclient, err := sdk.NewClient(&sdk.ClientConfig{
Model: sdk.ModelIdentifier{
Provider: sdk.APIProviderCodex,
Model: "gpt-5.3-codex",
},
CredentialResolver: VaultResolver{
Keys: map[string]string{
"codex": os.Getenv("CODEX_ACCESS_TOKEN"),
},
},
ProviderConfig: &providers.Config{
Provider: types.APIProviderCodex,
BaseURL: "https://chatgpt.com/backend-api/codex",
},
})The gRPC server does not perform an interactive OAuth handshake itself. The intended pattern is: do the login in your client app, obtain a live bearer token, then pass that token explicitly in the request that targets the Codex model.
resp, err := grpcClient.Query(ctx, &pb.QueryRequest{
Prompt: "Review this repository and propose a refactor plan.",
Model: "codex:gpt-5.3-codex",
ApiKey: accessToken,
Stream: false,
})Supported Provider Inventory
Once the scenario is clear, this table is the runtime-level source of truth for the provider ids, transports, endpoints, and auth modes currently understood by Seshat.
| Provider | Auth | Transport | Base / Endpoint | Notes |
|---|---|---|---|---|
| anthropic | API key | Anthropic Messages API | https://api.anthropic.com | Direct Claude provider. Prompt caching and structured output are first-class here, and DefaultConfigs() ships Claude-oriented aliases like sonnet and haiku. |
| openai | API key | OpenAI-compatible /chat/completions | https://api.openai.com/v1 | Primary GPT path. Also reused by the current image generation, text-to-speech, and speech-to-text capability providers. |
| ollama | None by default | Local /api/chat | http://localhost:11434 | Local inference path. Models are discovered dynamically from the local daemon rather than from a static registry list. |
| bedrock | AWS credentials | Anthropic-shaped Claude path via AWS | Region-driven integration | Used for Claude through AWS. Runtime validation expects AWS region configuration before the provider can be used safely. |
| vertex | GCP project + region | Anthropic-shaped Claude path via GCP | Project and region derived | Used for Claude on Google Cloud. Requires ANTHROPIC_VERTEX_PROJECT_ID and CLOUD_ML_REGION. |
| foundry | API key + resource header | Anthropic-shaped Claude path via Azure | https://your-resource.services.ai.azure.com/anthropic/v1 | Azure-hosted Claude path. The runtime sets the Foundry resource header when configured. |
| gemini | API key | Gemini generateContent API | https://generativelanguage.googleapis.com/v1beta | Very large context window path with audio-capable models. Current capability-provider options include Gemini for image generation. |
| z-ai | x-api-key | OpenAI-compatible body + custom auth header | https://api.z.ai/api/paas/v4 | GLM provider. The current client forces streaming for Z.ai requests and ships aliases such as glm-5 and glm-4.5. |
| openrouter | API key | OpenAI-compatible /chat/completions | https://openrouter.ai/api/v1 | Routing hub for multi-vendor models. Good fit when users want one key spanning Claude, GPT, DeepSeek, Qwen, or Llama families. |
| minimax | API key | MiniMax chatcompletion_v2 endpoint | https://api.minimax.chat/v1/text/chatcompletion_v2 | Long-output provider with strong multilingual and long-horizon positioning in the provider registry. |
| workers-ai | API key | Edge chat route with provider-specific model pathing | https://workers.ai/v1/chat | Cloudflare edge inference path. Model identifiers are route-style ids such as @cf/meta/llama-3.1-70b-instruct. |
| mistral | API key | OpenAI-compatible /chat/completions | https://api.mistral.ai/v1 | Direct Mistral provider with familiar OpenAI-compatible transport for the runtime. |
| codex | OAuth device flow | OpenAI Responses API /responses | https://chatgpt.com/backend-api/codex | Special path. Codex uses ChatGPT Pro OAuth, always streams, sends Codex-specific headers, and keeps a dedicated cookie jar for chatgpt.com / Cloudflare flows. |
| deepseek | API key | OpenAI-compatible /chat/completions | https://api.deepseek.com/v1 | Direct DeepSeek path with chat and reasoner aliases in DefaultConfigs(). |
| opencode | API key | OpenAI-compatible /chat/completions | https://opencode.ai/zen/v1 | Curated gateway exposing Claude, Codex, and GLM-style models behind one provider id. |
| kimi | API key | OpenAI-compatible target expected | No default base URL block in DefaultConfigs() | Kimi exists in the provider registry and string resolver, but internal/providers/config.go does not currently ship a matching default config entry. Treat it as partially wired until that gap is closed. |
Related Pages
Continue with Tools & Surface to shape what the authenticated runtime can actually do, or go back to Tools & Providers for the conceptual architecture behind the integration boundary.