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 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
/releaseskill separately.
Inputs
provider_id— the value users will pass viainputs.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 newaction.ymlinputs (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 onPATH),mcp_dest(relative path under$HOME, e.g..claude/mcp.json),install_command(npm-install or curl-installer snippet).
Pre-flight
# 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
<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:
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
DEFAULT_MODELS: dict[str, str] = {
"anthropic": "claude-sonnet-4-6",
"<provider_id>": "<default_model>",
}
4. Register in build_provider()
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:
<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✅ shippingonly 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]:
### 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:
### 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:
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
DEFAULT_MODELS: dict[str, str] = {
...,
"<provider_id>": "<default_model_or_auto>",
}
4. Register in build_provider()
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
<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
### 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.
- Chat-completions:
- The
# TODO(provider-implementer)markers placed in the new class. - Pointers to
.agents/agents/provider-implementer.mdfor 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.pypasses. -
action.ymlparses (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 viagrep). - Agent-runner scaffolds:
_build_cli_envused (not{**os.environ, ...}),shlex.split(extra_args)used (not string-concat),_invoke_cli_agentused (not baresubprocess.run).