Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ separately by `model.SchemaVersion` (currently 1.5.0).

## [Unreleased]

### Added
- **Cheaper Inference as an `explain` / `ask` provider.**
`PGBOT_AI_PROVIDER=cheaperinference` sends `/chat/completions` to
`https://api.cheaperinference.com/v1` with `CHEAPER_INFERENCE_API_KEY` (or
`PGBOT_AI_API_KEY`), the same way the `requesty` and `opper` aliases work. It
is never auto-detected, and it never picks up an OpenAI or OpenRouter key.

## [0.9.0] - 2026-10-18

### Added
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ one SSH connection serves the whole run. Raise `--timeout` if the link is slow.
| `OPENAI_API_KEY` / `OPENROUTER_API_KEY` | Enables `ask` / `explain` via OpenAI or OpenRouter. Keys are never accepted as flags. |
| `REQUESTY_API_KEY` | Enables `ask` / `explain` via [Requesty](https://docs.requesty.ai) with `PGBOT_AI_PROVIDER=requesty` (never auto-detected). |
| `OPPER_API_KEY` | Enables `ask` / `explain` via [Opper](https://docs.opper.ai) with `PGBOT_AI_PROVIDER=opper` (never auto-detected). |
| `CHEAPER_INFERENCE_API_KEY` | Enables `ask` / `explain` via [Cheaper Inference](https://cheaperinference.com/docs) with `PGBOT_AI_PROVIDER=cheaperinference` (never auto-detected). |
| `GEMINI_API_KEY` / `GOOGLE_API_KEY` | Enables `ask` / `explain` via Google Gemini. |
| `ANTHROPIC_API_KEY` | Enables `ask` / `explain` via Anthropic. |
| `XAI_API_KEY` / `GROK_API_KEY` | Enables `ask` / `explain` via xAI. |
Expand Down Expand Up @@ -783,7 +784,7 @@ not require confirmation.
| Bedrock Mantle | `AWS_BEARER_TOKEN_BEDROCK`, or `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` | `openai.gpt-5.6-terra` | Responses (GPT) / Messages (Claude) |

The OpenAI provider also supports compatible services such as OpenRouter,
Requesty, Opper, Groq, Together, DeepSeek, Mistral, Ollama, vLLM, and LM Studio.
Requesty, Opper, Cheaper Inference, Groq, Together, DeepSeek, Mistral, Ollama, vLLM, and LM Studio.
`PGBOT_AI_PROVIDER=requesty` with `REQUESTY_API_KEY` points it at
`https://router.requesty.ai/v1`; set `PGBOT_AI_MODEL` to any Requesty model id
(for example `openai/gpt-4o-mini`), and `PGBOT_AI_BASE_URL` to
Expand All @@ -792,6 +793,10 @@ Requesty, Opper, Groq, Together, DeepSeek, Mistral, Ollama, vLLM, and LM Studio.
`https://api.opper.ai/v3/compat`, an EU-hosted gateway. The default
`gpt-5.6-terra` works there as is; set `PGBOT_AI_MODEL` to any Opper model id
(for example `claude-sonnet-4-6`).
`PGBOT_AI_PROVIDER=cheaperinference` with `CHEAPER_INFERENCE_API_KEY` points it
at `https://api.cheaperinference.com/v1`. The default `gpt-5.6-terra` works
there as is; set `PGBOT_AI_MODEL` to any Cheaper Inference model id (for example
`gpt-5.4-mini`).

```
export OPENAI_API_KEY=…
Expand Down
1 change: 1 addition & 0 deletions internal/ai/openai.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
defaultOpenRouterURL = "https://openrouter.ai/api/v1"
defaultRequestyURL = "https://router.requesty.ai/v1"
defaultOpperURL = "https://api.opper.ai/v3/compat"
defaultCheaperInfURL = "https://api.cheaperinference.com/v1"
defaultOllamaURL = "http://localhost:11434/v1"

// Applied only to reasoning models. Override with $PGBOT_AI_REASONING_EFFORT
Expand Down
9 changes: 8 additions & 1 deletion internal/ai/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func Resolve() (LanguageModel, error) {
ReasoningEffort: envOr("PGBOT_AI_REASONING_EFFORT", ""),
}

case "openai", "openrouter", "requesty", "opper", "ollama", "openai-compatible":
case "openai", "openrouter", "requesty", "opper", "cheaperinference", "ollama", "openai-compatible":
// One /chat/completions client serves them all; only the endpoint and the
// conventional key variable differ.
if key == "" {
Expand All @@ -125,6 +125,9 @@ func Resolve() (LanguageModel, error) {
case "opper":
// Same for Opper.
key = firstEnv("OPPER_API_KEY")
case "cheaperinference":
// Same for Cheaper Inference.
key = firstEnv("CHEAPER_INFERENCE_API_KEY")
default:
key = firstEnv("OPENAI_API_KEY", "OPENROUTER_API_KEY")
}
Expand All @@ -143,6 +146,8 @@ func Resolve() (LanguageModel, error) {
base = defaultRequestyURL
case "opper":
base = defaultOpperURL
case "cheaperinference":
base = defaultCheaperInfURL
case "ollama":
base = defaultOllamaURL
case "openai-compatible":
Expand Down Expand Up @@ -210,6 +215,8 @@ func keyVarsFor(name string) string {
return "REQUESTY_API_KEY"
case "opper":
return "OPPER_API_KEY"
case "cheaperinference":
return "CHEAPER_INFERENCE_API_KEY"
default:
return "OPENAI_API_KEY / OPENROUTER_API_KEY"
}
Expand Down
36 changes: 35 additions & 1 deletion internal/ai/resolve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func clearEnv(t *testing.T) {
for _, k := range []string{
"PGBOT_AI_PROVIDER", "PGBOT_AI_MODEL", "PGBOT_AI_BASE_URL", "PGBOT_AI_API_KEY",
"PGBOT_AI_REASONING_EFFORT", "GEMINI_API_KEY", "GOOGLE_API_KEY", "ANTHROPIC_API_KEY",
"OPENAI_API_KEY", "OPENROUTER_API_KEY", "REQUESTY_API_KEY", "OPPER_API_KEY", "XAI_API_KEY", "GROK_API_KEY",
"OPENAI_API_KEY", "OPENROUTER_API_KEY", "REQUESTY_API_KEY", "OPPER_API_KEY", "CHEAPER_INFERENCE_API_KEY", "XAI_API_KEY", "GROK_API_KEY",
"AWS_BEARER_TOKEN_BEDROCK", "AWS_REGION", "AWS_DEFAULT_REGION",
"PGBOT_GEMINI_MODEL", "PGBOT_GEMINI_URL", "PGBOT_OPENAI_MODEL", "PGBOT_OPENAI_URL",
} {
Expand Down Expand Up @@ -315,6 +315,40 @@ func TestResolve_opper(t *testing.T) {
}
}

// cheaperinference is an explicit alias only: it goes to Cheaper Inference with
// CHEAPER_INFERENCE_API_KEY or PGBOT_AI_API_KEY, never borrows an OpenAI key,
// and is never auto-detected.
func TestResolve_cheaperinference(t *testing.T) {
for _, keyVar := range []string{"CHEAPER_INFERENCE_API_KEY", "PGBOT_AI_API_KEY"} {
clearEnv(t)
t.Setenv("PGBOT_AI_PROVIDER", "cheaperinference")
t.Setenv(keyVar, "k")
m, err := Resolve()
if err != nil {
t.Fatalf("cheaperinference with %s: %v", keyVar, err)
}
if m.Provider() != "openai" {
t.Errorf("cheaperinference with %s: provider = %q, want openai", keyVar, m.Provider())
}
if m.Endpoint() != defaultCheaperInfURL {
t.Errorf("cheaperinference with %s: endpoint = %q, want %q", keyVar, m.Endpoint(), defaultCheaperInfURL)
}
}

clearEnv(t)
t.Setenv("PGBOT_AI_PROVIDER", "cheaperinference")
t.Setenv("OPENAI_API_KEY", "sk-openai")
if _, err := Resolve(); err == nil || !strings.Contains(err.Error(), "CHEAPER_INFERENCE_API_KEY") {
t.Errorf("cheaperinference with only OPENAI_API_KEY should ask for CHEAPER_INFERENCE_API_KEY, got: %v", err)
}

clearEnv(t)
t.Setenv("CHEAPER_INFERENCE_API_KEY", "k")
if _, err := Resolve(); err == nil {
t.Error("CHEAPER_INFERENCE_API_KEY alone must not be auto-detected")
}
}

// A custom remote endpoint must come with a named provider. Auto-detecting one
// from an ambient vendor key would send that key — say the ANTHROPIC_API_KEY
// another tool left in the shell — to whatever host PGBOT_AI_BASE_URL names,
Expand Down