Model providers
On the hosted app the agent is Screenplay’s own loop, built on the Vercel AI SDK. It runs on whichever model providers you configure. Each provider turns on when its environment variables are set, and configured providers appear as sections in the chat’s model picker.
The desktop app doesn’t use these providers. Its chat runs through the coding CLIs installed on the machine. See Coding CLIs.
Providers
At least one provider is required.
-
Model lists are live. Each provider queries its API for models, caches the list for an hour, and falls back to a small built-in list if the query fails (bad key, rate limit, a server without
/v1/models). New upstream models show up without a Screenplay release. -
Vercel AI Gateway gives you hundreds of models behind one key, plus budgets, analytics, and failover. Set
AI_GATEWAY_API_KEYexplicitly, even on Vercel: the gateway doesn’t accept the OIDC token Vercel injects. -
OpenAI-compatible covers OpenRouter, Groq, Together, vLLM, LM Studio, Ollama, LiteLLM, and similar:
OPENAI_COMPATIBLE_BASE_URL=https://openrouter.ai/api/v1 OPENAI_COMPATIBLE_API_KEY=sk-or-...
Model ids and the default
Model ids always name their provider: <provider>:<model>, for example
anthropic:claude-sonnet-5-5, openai:gpt-6-astra,
vercel:anthropic/claude-sonnet-5.5, or compat:llama-3.3-70b. Ids without a
provider are rejected, so a request can never quietly reach a provider you
didn’t intend.
AGENT_DEFAULT_MODEL sets the default for new chats. It defaults to
anthropic:claude-sonnet-5-5. If you don’t run Anthropic, point it at a
provider you do. If its provider isn’t configured, new chats use the first
enabled model instead. Each user’s last-picked model is remembered in their browser.
A chat whose model belongs to a provider that’s no longer configured fails with that provider’s error. It’s never rerouted to a different provider.
Adding a provider
Providers live in apps/app/lib/agent/providers/, one file each, behind the
ModelProvider interface in types.ts:
import "server-only"
import { mistral } from "@ai-sdk/mistral"
import type { ModelProvider } from "./types"
class MistralProvider implements ModelProvider {
key = "mistral"
label = "Mistral"
isConfigured() {
return Boolean(process.env.MISTRAL_API_KEY)
}
async listModels() {
if (!this.isConfigured()) return []
return [{ id: "mistral:mistral-large-latest", label: "Mistral Large" }]
}
resolve(modelId: string) {
return mistral(modelId)
}
// Lets terminal-tab CLIs reach this provider without seeing the key:
// the sandbox firewall adds these headers for this host. Return null to opt out.
egress() {
const key = process.env.MISTRAL_API_KEY
return key ? { host: "api.mistral.ai", headers: { authorization: `Bearer ${key}` } } : null
}
}
export function getMistralProvider(): ModelProvider {
return new MistralProvider()
}Install the adapter (pnpm add @ai-sdk/mistral in apps/app), then add the
factory to the PROVIDERS array in providers/index.ts. The agent, the model
picker, and /api/agent/models need no other changes. Use the existing
providers’ discover() helper from cache.ts for a live, cached model list.