Build a model provider
Write a provider plugin only when the model API uses a protocol TAI cannot already speak. The plugin owns the wire-format translation; the rest of the runtime continues to work with provider-neutral messages, tool calls, usage, and finish reasons.
Before you write code
If the service accepts OpenAI-style /v1/chat/completions requests, configure
the built-in adapter under any id:
providers:
acme:
type: openai_compatible
baseUrl: https://api.acme.example/v1
apiKey: ${ACME_API_KEY}
defaultModel: acme-large
That path already supports chat, streaming, tool calls, model discovery, and multiple differently named endpoints. A plugin is warranted when authentication, request bodies, response events, or tool-call semantics are materially different.
The contract
An AIProvider has three required properties and one required method:
import type {
AIProvider,
ChatParams,
ChatResponse,
} from "@tailored-ai/core";
class AcmeProvider implements AIProvider {
id = "acme";
name = "Acme Models";
supportsTools = true;
constructor(private apiKey: string) {}
async chat(params: ChatParams): Promise<ChatResponse> {
// 1. Map params.messages and params.tools to Acme's request shape.
// 2. Send the request with params.model and this.apiKey.
// 3. Map Acme's reply back to TAI's provider-neutral response.
return {
content: "mapped response",
toolCalls: [],
usage: { input: 0, output: 0 },
finishReason: "stop",
};
}
}
The response must always include usage and one of the three portable finish
reasons: stop, tool_calls, or length. If supportsTools is true, map
both directions: TAI's function schemas into the request and the model's calls
back into { id, name, arguments } objects.
Two optional methods improve the experience:
chatStream(params)yields text or reasoning deltas and ends with onedoneevent containing the complete response.listModels()returns model ids fortai initand the configuration editor.
Register the provider
Export a default plugin function and register a factory. The factory reads the provider's config, constructs the adapter, and returns the selected model:
import type {
AgentConfig,
Plugin,
PluginMeta,
} from "@tailored-ai/core";
interface AcmeConfig {
apiKey?: string;
defaultModel?: string;
}
export const meta: PluginMeta = {
name: "Acme provider",
description: "Acme models through the native Acme API.",
registers: [
{ kind: "provider", id: "acme", configKey: "providers.acme" },
],
};
export function validateConfig(config: AgentConfig): string[] {
const cfg = config.providers.acme as AcmeConfig | undefined;
if (!cfg) return [];
const warnings: string[] = [];
if (!cfg.apiKey) warnings.push("providers.acme.apiKey is required");
if (!cfg.defaultModel) {
warnings.push("providers.acme.defaultModel is required");
}
return warnings;
}
const plugin: Plugin = (ctx) => {
ctx.providers.register("acme", (config) => {
const cfg = config.providers.acme as AcmeConfig | undefined;
if (!cfg?.apiKey) throw new Error("providers.acme requires apiKey");
if (!cfg.defaultModel) {
throw new Error("providers.acme requires defaultModel");
}
return {
provider: new AcmeProvider(cfg.apiKey),
model: cfg.defaultModel,
};
});
};
export default plugin;
meta makes the extension understandable in tai plugin list and the API.
validateConfig reports actionable startup warnings; the factory should still
fail clearly when it cannot construct a usable provider.
Package it
Keep @tailored-ai/core as a peer dependency so the plugin uses the runtime's
types and registries instead of bundling another copy:
{
"name": "@your-org/tai-provider-acme",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"peerDependencies": {
"@tailored-ai/core": "^0.1.0"
},
"keywords": ["tailored-ai", "tai-plugin", "provider"]
}
Publish the compiled ESM package, then install and select it exactly like a first-party provider:
tai plugin install @your-org/tai-provider-acme
providers:
acme:
apiKey: ${ACME_API_KEY}
defaultModel: acme-large
agent:
defaultProvider: acme
Finish with a plain chat request, a tool-calling request, and—if implemented—a
streaming request. @tailored-ai/provider-bedrock
is the most complete reference for a non-OpenAI protocol; the smaller provider
packages are useful examples of configuration, metadata, and validation.