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 normalizeModel function in src/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-5 or openai/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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →