# Add Provider

> Scaffold a new LLM Provider implementation in scripts/reviewer.py — class, registry entry, default model, action.yml inputs, docs updates. Implementing the actual translation logic is the user's job.

- Skill: `dailybothq/add-provider` (Agent Skill)
- Install (CLI): `npx skillmds@latest add dailybothq/add-provider`
- Raw SKILL.md: https://api.skillmd.com/api/skills/dailybothq/add-provider/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: dailybothq (https://skillmd.com/u/dailybothq)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/dailybothq/add-provider

---


# Skill: Add Provider

## Objective

Scaffold a new LLM provider in `scripts/reviewer.py` following the pattern set by the shipping references — `AnthropicProvider` for the chat-completions family or `ClaudeCodeProvider` / `CursorProvider` / `CodexProvider` for the agent-runner family. Adds the class skeleton, the `build_provider()` registry entry, the `DEFAULT_MODELS` mapping, any provider-specific `action.yml` inputs (including CLI install steps for agent-runners), and the corresponding doc updates.

The skill produces the **scaffold and the doc trail**; the actual message/response translation (chat-completions) or the CLI subprocess wiring specifics (agent-runner) are the user's job. See [.agents/agents/provider-implementer.md](../../agents/provider-implementer.md) for the deep dive on both families.

## Family selection — ASK FIRST

Before scaffolding, ask the user which family the new provider belongs to:

- **Chat-completions (`Provider`)** — the vendor exposes a raw HTTP messages/completions API and you own the tool-use loop (Anthropic, OpenAI, Gemini, Bedrock, vLLM/Ollama).
- **Agent-runner (`AgentRunnerProvider`)** — the vendor ships a headless coding-agent CLI you shell out to; the CLI writes `.aiprr/findings.json` (Claude Code, Cursor Agent, OpenAI Codex, Aider, Continue, ...).

The rest of this skill has two implementation branches; run the one matching the family.

## Non-goals

- Does NOT implement chat-completions translation logic (each provider's API shape differs).
- Does NOT invent CLI subprocess quirks for agent-runner providers — you must confirm the CLI flags with vendor docs.
- Does NOT add non-stdlib dependencies. If the provider can't be implemented stdlib-only, stop and surface that to the user — it's a design decision, not a workaround.
- Does NOT cut a release. After scaffolding, the user merges, smoke-tests, and uses the `/release` skill separately.

## Inputs

- `provider_id` — the value users will pass via `inputs.provider` (e.g. `openai`, `azure-openai`, `google`, `bedrock`, `vllm`, `claude-code`, `cursor`, `codex`).
- `provider_name` — human-readable name for docstrings and docs (e.g. "OpenAI", "Azure OpenAI", "Google Gemini", "Claude Code", "Cursor Agent").
- `provider_family` — `"chat-completions"` or `"agent-runner"`.
- `default_model` — recommended default model id for the provider (or `"auto"` sentinel for agent-runner if the CLI picks its own default).
- `provider_inputs` — optional list of new `action.yml` inputs (e.g. `["azure-resource", "azure-deployment", "azure-api-version"]` for Azure, or the per-CLI version pin like `<provider_id>-version`).
- For chat-completions: `api_url` — the provider's HTTP endpoint.
- For agent-runner: `cli_bin` (binary name on `PATH`), `mcp_dest` (relative path under `$HOME`, e.g. `.claude/mcp.json`), `install_command` (npm-install or curl-installer snippet).

## Pre-flight

```bash
# Read the contract (both families)
cat docs/PROVIDERS.md

# Read the reference impl(s)
grep -n "class AnthropicProvider\|class ClaudeCodeProvider\|class CursorProvider\|class CodexProvider" scripts/reviewer.py

# Confirm we have an [Unreleased] section ready
grep -A1 "^## \[Unreleased\]" CHANGELOG.md
```

If `docs/PROVIDERS.md` doesn't list the new provider in its roadmap table, ask the user whether to add it before scaffolding.

## Steps — chat-completions family

### 1. Add URL constants near `ANTHROPIC_API_URL`

```python
<NAME>_API_URL: str = "<api_url>"
# Add any version/auth headers as further constants here.
```

### 2. Add the provider class after `AnthropicProvider`

Skeleton with a clear `# TODO` for the translation work:

```python
class <Name>Provider(Provider):
    """<provider_name> Messages API client.

    Translates Anthropic-shape messages/tools to <provider>'s
    <chat-completions / generateContent / etc.> schema, and translates
    the response back to Anthropic-shape `content` blocks for the
    agentic loop in `drive_review()`.
    """

    def __init__(self, *, api_key: str, model: str) -> None:
        self.api_key: str = api_key
        self.model: str = model

    def complete(
        self,
        *,
        system_prompt: str,
        messages: list[dict[str, Any]],
        tools: list[dict[str, Any]],
    ) -> dict[str, Any]:
        # TODO(provider-implementer): translate Anthropic-shape input to
        # <provider>'s request shape. Pattern to mirror from
        # AnthropicProvider:
        #   - bounded retries on 429/5xx via API_RETRY_DELAYS_S
        #   - returns dict with stop_reason + content[] in Anthropic shape
        raise NotImplementedError(
            "<provider_id> provider scaffold — translation logic pending."
        )
```

### 3. Add to `DEFAULT_MODELS`

```python
DEFAULT_MODELS: dict[str, str] = {
    "anthropic": "claude-sonnet-4-6",
    "<provider_id>": "<default_model>",
}
```

### 4. Register in `build_provider()`

```python
def build_provider(provider_id: str, *, api_key: str, model: str) -> Provider:
    if provider_id == "anthropic":
        return AnthropicProvider(api_key=api_key, model=model)
    if provider_id == "<provider_id>":
        return <Name>Provider(api_key=api_key, model=model)
    raise ValueError(...)
```

### 5. Add provider-specific inputs to `action.yml` (if any)

For each input in `provider_inputs`:

```yaml
  <input-name>:
    description: '<concise description>. Used only when `provider: <provider_id>`.'
    required: false
    default: ''
```

Forward each to the runtime via `AIPRR_<INPUT_NAME>` in the composite action's `env:` block, and read it from `os.environ` in `main()`.

### 6. Update `docs/PROVIDERS.md`

- Flip the status table entry to `🟡 scaffolded (translation pending)` until the implementation is complete; flip to `✅ shipping` only after smoke tests pass.
- Add a section under "Specific gotchas per planned provider" if not already present, capturing translation notes the implementer is going to need.

### 7. Update `README.md`

- Provider roadmap table at the bottom.
- Inputs table if you added new inputs.

### 8. Update `CHANGELOG.md`

Under `[Unreleased]`:

```markdown
### Added (in progress)
- `<provider_id>` provider scaffold — class, registry, default model.
  Translation logic pending; not yet user-invocable.
```

When the implementation is complete, the entry promotes to:

```markdown
### Added
- `<provider_id>` provider — translates Anthropic-shape messages/tools to
  <provider_name>'s API. Default model: <default_model>. See docs/PROVIDERS.md.
```

## Steps — agent-runner family

### 1. Add class attributes constants block near the other AgentRunnerProviders

Nothing to add at module scope; the constants live on the class itself (`CLI_NAME`, `CLI_BIN`, `MCP_DEST`).

### 2. Add the provider class after `CodexProvider`

Skeleton with a clear `# TODO` for CLI-flag confirmation:

```python
class <Name>Provider(AgentRunnerProvider):
    """<provider_name> agent-runner client.

    Shells out to the `<cli_bin>` CLI; the CLI owns the tool-use loop and
    writes findings to `.aiprr/findings.json` via the schema documented in
    docs/PROVIDERS.md.
    """

    CLI_NAME: str = "<provider_id>"
    CLI_BIN: str = "<cli_bin>"
    MCP_DEST: str = "<mcp_dest>"  # e.g. ".<vendor>/mcp.json"

    def __init__(
        self,
        *,
        api_key: str,
        model: str,
        extra_args: str,
        mcp_config_file: str,
    ) -> None:
        self.api_key: str = api_key
        self.model: str = model
        self.extra_args: str = extra_args
        self.mcp_config_file: str = mcp_config_file

    def install(self) -> None:
        # Defensive check — install is done in action.yml, this verifies PATH.
        run_cmd([self.CLI_BIN, "--version"])

    def run_review(
        self,
        *,
        pr_context: PRContext,
        review_instructions: str,
        workspace: Path,
        output_dir: Path,
    ) -> ReviewResult:
        findings_path: Path = output_dir / FINDINGS_JSON_REL
        findings_path.parent.mkdir(parents=True, exist_ok=True)
        instructions: str = write_findings_prompt_directive(
            review_instructions, findings_path
        )
        backup = _swap_mcp_config(self.mcp_config_file, self.MCP_DEST)
        try:
            argv: list[str] = [
                self.CLI_BIN,
                # TODO(provider-implementer): confirm the CLI's flags for:
                #   - user prompt (positional? -p? stdin?)
                #   - model selection
                #   - non-interactive/headless mode
                #   - workspace / cwd
            ]
            if self.model and self.model != "auto":
                argv += ["--model", self.model]
            if self.extra_args:
                argv += shlex.split(self.extra_args)
            env: dict[str, str] = _build_cli_env(
                extra_vars={"<VENDOR>_API_KEY": self.api_key}
            )
            return _invoke_cli_agent(
                argv=argv,
                workspace=workspace,
                findings_path=findings_path,
                env=env,
                cli_name=self.CLI_NAME,
            )
        finally:
            _restore_mcp_config(backup, self.MCP_DEST)
```

### 3. Add to `DEFAULT_MODELS`

```python
DEFAULT_MODELS: dict[str, str] = {
    ...,
    "<provider_id>": "<default_model_or_auto>",
}
```

### 4. Register in `build_provider()`

```python
if provider_id == "<provider_id>":
    return <Name>Provider(
        api_key=api_key,
        model=model,
        extra_args=extra_args,
        mcp_config_file=mcp_config_file,
    )
```

### 5. Add install step in `action.yml`

Guarded by `if: inputs.provider == '<provider_id>'`. Follow the shape of the existing Claude Code / Cursor / Codex install steps — set up Node if needed, npm-install (or curl-installer) the CLI, honour the optional `<provider_id>-version` input.

### 6. Add the `<provider_id>-version` input in `action.yml`

```yaml
  <provider_id>-version:
    description: 'Optional version pin for the <provider_name> CLI. Empty = latest.'
    required: false
    default: ''
```

Forward via `AIPRR_<PROVIDER_ID>_VERSION` if the runtime needs it (usually not — the install step consumes it directly).

### 7. Update `docs/PROVIDERS.md`

Add an entry to the Agent Runner Provider Contract section — CLI binary name, MCP destination, install command, any provider-specific gotchas.

### 8. Update `README.md`

- Provider roadmap table (`Family: agent-runner`).
- Inputs table (the new version-pin input).

### 9. Update `.github/workflows/self-review.yml`

Add a matrix leg for the new provider so dogfooding covers it.

### 10. Update `.github/workflows/code_check.yml`

Add a matrix leg to the `cli-install-smoke` job so installer drift is caught in CI.

### 11. Add an example workflow

`examples/provider-<provider_id>.yml` — copy-paste consumer setup.

### 12. Update `CHANGELOG.md`

```markdown
### Added
- `<provider_id>` agent-runner provider — shells out to the `<cli_bin>` CLI;
  findings via `.aiprr/findings.json`. Default model: `<default_model>`.
  See docs/PROVIDERS.md.
```

## Output to user

After running, summarise what was scaffolded:

- Files modified:
  - Chat-completions: `scripts/reviewer.py`, `action.yml` (if inputs added), `docs/PROVIDERS.md`, `README.md`, `CHANGELOG.md`.
  - Agent-runner: same as above **plus** `.github/workflows/self-review.yml`, `.github/workflows/code_check.yml`, `examples/provider-<provider_id>.yml`.
- The `# TODO(provider-implementer)` markers placed in the new class.
- Pointers to `.agents/agents/provider-implementer.md` for the next step.

Remind the user that the scaffold currently raises `NotImplementedError` (chat-completions) or has TODO-marked CLI flags to verify (agent-runner) — it doesn't ship until the translation / CLI wiring is implemented and smoke-tested.

## Quality gates after scaffolding

- [ ] `python3 -m py_compile scripts/reviewer.py` passes.
- [ ] `action.yml` parses (`python3 -c 'import yaml; yaml.safe_load(open("action.yml"))'` — CI-only tool, not runtime).
- [ ] No new non-stdlib **runtime** imports introduced.
- [ ] All TODO markers reference `provider-implementer` (so they're discoverable via `grep`).
- [ ] Agent-runner scaffolds: `_build_cli_env` used (not `{**os.environ, ...}`), `shlex.split(extra_args)` used (not string-concat), `_invoke_cli_agent` used (not bare `subprocess.run`).

