AI config
Provider-agnostic AI configuration with agent-assisted setup.
@caisson/ai-config is one typed configuration surface for AI providers. It is provider-agnostic:
the base depends on the config shape, not on a single vendor's SDK, so the driver is swappable
without touching call sites.
The contract
Configuration is declared once, validated at the boundary, and read everywhere through the same typed accessor, no provider details leak into feature code.
import { parseAiSettings, resolveProvider } from "@caisson/ai-config";
const settings = parseAiSettings({
defaultLane: "chat",
lanes: {
chat: {
provider: "anthropic",
model: "claude-sonnet",
apiKeyEnv: "ANTHROPIC_API_KEY",
},
cheap: {
provider: "openrouter",
model: "meta-llama/llama-3.1-8b",
apiKeyEnv: "OPENROUTER_API_KEY",
},
},
});
// Resolves by name, or falls back to defaultLane.
const chat = resolveProvider(settings, "chat");Setup is agent-assisted: the shipped MCP server can drive configuration on your behalf, so an AI agent wires the providers instead of you hand-editing every field.
API reference
@caisson/ai-config exports two functions and their types. It is pure logic, no network calls,
no SDK imports, so resolving a lane never touches a provider until your own call site does.
Settings shape
type ProviderConfig = {
provider:
| "openai"
| "anthropic"
| "google"
| "openrouter"
| "local"
| "bedrock"
| "azure-openai"
| "ollama"
| "groq"
| "mistral"
| "together";
model: string;
keySource?: "env" | "tenant";
apiKeyEnv?: string;
baseUrl?: string;
region?: string;
apiVersion?: string;
apiSecretEnv?: string;
};
type AiSettings = {
defaultLane: string;
lanes: Record<string, ProviderConfig>;
};AiSettings is a named-lane map: one defaultLane plus any number of lanes, each a
ProviderConfig binding a provider + model to a lane name. The provider is always chosen from
config (no provider name is hardcoded in the package) so adding a vendor or moving a lane to a
different one is a config edit, not a code change.
The package never reads a key's value, only where it lives:
keySource: "env"(default):apiKeyEnvnames the environment variable holding the key. Required for every provider exceptbedrock, which falls back to the AWS default credential chain when bothapiKeyEnvandapiSecretEnvare omitted.keySource: "tenant": the key is stored encrypted per tenant and resolved at inference time elsewhere. A tenant lane must not nameapiKeyEnv.bedrocktakes a two-part credential (apiKeyEnvfor the access key id,apiSecretEnvfor the secret) and an optionalregion.azure-openaiaddresses a deployment throughmodel, soapiVersionandbaseUrl(the Azure resource endpoint) are both required.localandollamaare OpenAI-API-compatible and take abaseUrl;groq,mistral, andtogetherare OpenAI-API-compatible hosted vendors with a hardcoded defaultbaseUrlyou can still override, same asopenrouter.
Parsing and resolution
function parseAiSettings(input: unknown): AiSettings;
function resolveProvider(settings: AiSettings, lane?: string): ProviderConfig;parseAiSettings validates input against the schema above with .strict(): an unrecognized
top-level key, a missing required field (like a non-bedrock lane without apiKeyEnv, or an
azure-openai lane without apiVersion/baseUrl), or a tenant lane that names apiKeyEnv
all fail closed. On failure it throws a ValidationError (HTTP 400) carrying the failing field
paths and error codes, never a stack trace or the full input object.
resolveProvider looks up lane in settings.lanes, or settings.defaultLane when lane is
omitted. An unconfigured lane throws a NotFoundError (HTTP 404) rather than returning
undefined, so a typo'd lane name fails at the call site instead of silently falling through to
whatever provider happens to be default.