Model Normalization Across Different AI Providers in the Compound-Engineering Plugin
The Compound-Engineering Plugin automatically converts Claude Code plugins to OpenCode format by normalizing model identifiers through the normalizeModel function in src/converters/claude-to-opencode.ts, which maps aliases like "haiku" to fully-qualified strings like "anthropic/claude-haiku-4-5" while supporting Anthropic, OpenAI, and Google providers.
The EveryInc/compound-engineering-plugin bridges Claude Code plugins with the OpenCode ecosystem, requiring seamless translation of model identifiers across incompatible provider formats. Model normalization across different AI providers ensures that short aliases, partial names, and fully-qualified identifiers all resolve to consistent, provider-prefixed strings that downstream writers can interpret unambiguously.
How Model Normalization Works in claude-to-opencode.ts
The normalization logic lives in src/converters/claude-to-opencode.ts and follows a strict four-step precedence chain to resolve any input string into a qualified identifier.
The normalizeModel Function Implementation
The core normalizeModel function (lines 261-274) implements the resolution logic:
function normalizeModel(model: string): string {
// 1️⃣ Already qualified? → return unchanged
if (model.includes("/")) return model
// 2️⃣ Short Claude alias? → map via CLAUDE_FAMILY_ALIASES
if (CLAUDE_FAMILY_ALIASES[model]) {
const resolved = `anthropic/${CLAUDE_FAMILY_ALIASES[model]}`
console.warn(`Warning: bare model alias "${model}" mapped to "${resolved}". …`);
return resolved
}
// 3️⃣ Model name starts with a known provider prefix → prepend the proper namespace
if (/^claude-/.test(model)) return `anthropic/${model}`
if (/^(gpt-|o1-|o3-)/.test(model)) return `openai/${model}`
if (/^gemini-/.test(model)) return `google/${model}`
// 4️⃣ Fallback – assume it is a Claude model
return `anthropic/${model}`
}
Claude Family Aliases Mapping
Lines 255-259 define the CLAUDE_FAMILY_ALIASES table that maps convenient short names to official Claude model identifiers:
const CLAUDE_FAMILY_ALIASES: Record<string, string> = {
haiku: "claude-haiku-4-5",
sonnet: "claude-sonnet-4-5",
opus: "claude-opus-4-5",
}
When a plugin author specifies model: haiku, the normalization process emits anthropic/claude-haiku-4-5 and logs a warning about the alias resolution.
Provider-Specific Normalization Rules
The plugin recognizes three major AI providers through regex pattern matching, ensuring that model identifiers from different ecosystems receive the correct namespace prefix.
Anthropic Claude Models
Any string beginning with claude- is prefixed with anthropic/. This catches full model names like claude-sonnet-4-5 that users might provide without the provider prefix. The alias table handles the shorter nicknames (haiku, sonnet, opus) before this regex check occurs.
OpenAI GPT and O-Series Models
OpenAI identifiers matching ^gpt-, ^o1-, or ^o3- receive the openai/ prefix. This covers current and future GPT-4 variants, reasoning models like o1-preview, and upcoming o3 series models, ensuring they resolve to strings like openai/gpt-4o or openai/o1-mini.
Google Gemini Models
Strings starting with gemini- are prefixed with google/, converting identifiers like gemini-1.5-flash into fully-qualified google/gemini-1.5-flash strings that downstream writers can route to the correct Google AI API endpoints.
Integration with Agent and Command Conversion
The normalization logic is invoked at specific points during the conversion pipeline to ensure every model reference is resolved before writing the OpenCode output.
Normalizing Agent Models
During agent conversion (lines 95-96), when an agent declares a model field that is not "inherit", the value passes through normalizeModel:
// From convertAgent function
if (agent.model && agent.model !== "inherit") {
frontmatter.model = normalizeModel(agent.model);
}
This ensures that the OpenCode frontmatter receives a fully-qualified identifier regardless of how the original Claude plugin specified the model.
Normalizing Command-Level Model Overrides
Command conversion (lines 22-24) applies the same normalization to command-specific model overrides:
// From convertCommands function
if (command.model) {
commandMap[command.name].model = normalizeModel(command.model);
}
Commands that set disable-model-invocation: true are skipped entirely, preserving the original intent while ensuring that any active model reference is properly qualified.
Code Examples
Normalizing a Bare Claude Alias
import { normalizeModel } from "./converters/claude-to-opencode";
const raw = "haiku";
const qualified = normalizeModel(raw);
// qualified === "anthropic/claude-haiku-4-5"
Converting an Agent with a Model Field
// Claude plugin snippet
{
name: "Security Auditor",
description: "Audits code for vulnerabilities",
model: "gpt-4o", // OpenAI model, short form
body: "...",
}
During conversion (convertAgent), the plugin produces:
# OpenCode front‑matter (excerpt)
model: openai/gpt-4o
Command-Level Model Override
// In a Claude command
{
name: "write-doc",
description: "Generates documentation",
model: "sonnet", // Bare Claude alias
body: "...",
}
After convertCommands:
{
"write-doc": {
"description": "Generates documentation",
"template": "...",
"model": "anthropic/claude-sonnet-4-5"
}
}
Handling an Already-Qualified Identifier
normalizeModel("google/gemini-1.5-flash");
// Returns "google/gemini-1.5-flash" unchanged
Summary
- The Compound-Engineering Plugin converts Claude Code plugins to OpenCode format using the
normalizeModelfunction insrc/converters/claude-to-opencode.ts. - Model normalization across different AI providers follows a four-step precedence: fully-qualified IDs pass through unchanged, bare Claude aliases map to official names, regex patterns detect provider families (Anthropic, OpenAI, Google), and unknown strings default to Anthropic.
- The logic handles agent-level (lines 95-96) and command-level (lines 22-24) model declarations, ensuring every OpenCode output contains provider-prefixed identifiers like
anthropic/claude-sonnet-4-5oropenai/gpt-4o. - Downstream writers (Pi, Gemini, Codex targets) receive consistent, fully-qualified model strings regardless of the original plugin's alias format.
Frequently Asked Questions
What happens if I use a bare alias like "sonnet" in my Claude plugin?
The normalizeModel function detects the bare alias through the CLAUDE_FAMILY_ALIASES table (lines 255-259) and maps it to the fully-qualified identifier anthropic/claude-sonnet-4-5. A warning is logged to inform you that the alias was resolved, encouraging the use of fully-qualified names in future configurations.
How does the plugin handle already qualified model identifiers?
If the input string contains a forward slash (/), the function returns it unchanged immediately (line 263). This allows users to specify exact provider/model combinations like google/gemini-1.5-flash or openai/gpt-4o without interference from the normalization logic.
Which AI providers are supported by the normalization logic?
The plugin recognizes three major provider families through regex pattern matching (lines 270-272): Anthropic (patterns starting with claude-), OpenAI (patterns starting with gpt-, o1-, or o3-), and Google (patterns starting with gemini-). Any unrecognized string defaults to the Anthropic namespace as a fallback.
What is the difference between agent-level and command-level model normalization?
During agent conversion (convertAgent, lines 95-96), the plugin normalizes the model field from the agent definition into the OpenCode frontmatter. During command conversion (convertCommands, lines 22-24), it normalizes model overrides specified at the individual command level. Both paths use the same normalizeModel function, ensuring consistency whether the model is defined globally for an agent or locally for a specific command.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →