Skip to content

feat(llm): add Opper provider - #1139

Merged
zzet merged 3 commits into
zzet:mainfrom
Felixkw12:feat/opper-provider
Oct 31, 2026
Merged

zzet merged 3 commits into
zzet:mainfrom
Felixkw12:feat/opper-provider

Conversation

@Felixkw12

Copy link
Copy Markdown
Contributor

Summary

Adds opper as a built-in remote LLM provider. Opper is an EU-hosted, OpenAI-compatible gateway (https://api.opper.ai/v3/compat) with bare pool model ids such as gpt-5.4-mini or claude-sonnet-4-6. The wiring mirrors the existing requesty provider, and the provider reuses openaicompat.Client with strict json_schema structured output. Closes #.

Disclosure: Opper is my startup.

Changes

  • internal/llm/provider/opper/opper.go (new): builds an openaicompat.Client for <base>/chat/completions. Key from api_key_env (default OPPER_API_KEY). The base URL already carries /v3/compat, so nothing is inserted before /chat/completions. Forwards effort as reasoning_effort.
  • internal/llm/config.go: Opper RemoteConfig, builtinProviders, ActiveModel, WithModel, IsEnabled, MergeEnv (model and effort), ApplyDefaults (gpt-5.4-mini, the base URL above, OPPER_API_KEY), MergedWith.
  • internal/llm/provider/provider.go: case "opper" and the error message. prompts.go: opper maps to ProfileFrontier.
  • Guide, assist description, docs/llm.md, README.md, SECURITY.md, CLAUDE.md.
  • Tests: opper_test.go (missing key and model, json_schema request, base URL path, freeform, API error, reasoning_effort), plus config, provider, prompts and guide marker tests.

The embedding key fallback from the Requesty change is left out: embedOpenAI appends /v1/embeddings unless the base ends in /v1, which would not match Opper's /v3/compat/embeddings.

Testing

  • All tests pass (go test -race ./...) (not run locally, no Go toolchain here; CI will run it)
  • New tests added for new functionality
  • Benchmarks run if performance-relevant (not applicable)

Checklist

  • Code follows existing patterns in the codebase
  • No unnecessary abstractions added
  • Language extractor includes Meta["methods"] for interfaces (not applicable)
  • Methods have EdgeMemberOf edges to their containing type (not applicable)

Adds `opper` as a built-in remote LLM provider. Opper is an EU-hosted,
OpenAI-compatible gateway (`https://api.opper.ai/v3/compat`) that
fronts many vendors behind one key, with bare pool model ids such as
`gpt-5.4-mini` or `claude-sonnet-4-6`. The wiring mirrors the existing
`requesty` provider (RemoteConfig block, defaults, env overrides,
factory case), and the provider reuses `openaicompat.Client` with
strict `json_schema` structured output, same as `openai`.

- `internal/llm/provider/opper/opper.go` (new): `New(cfg
  llm.RemoteConfig)` builds an `openaicompat.Client` for
  `<base>/chat/completions`. Key from `api_key_env` (default
  `OPPER_API_KEY`); the base URL already carries `/v3/compat`, so
  nothing is inserted before `/chat/completions`. Forwards `effort` as
  `reasoning_effort`.
- `internal/llm/config.go`: `Opper RemoteConfig`, `builtinProviders`,
  `ActiveModel`, `WithModel`, `IsEnabled`, `MergeEnv` (model and
  effort), `ApplyDefaults` (`gpt-5.4-mini`, the base URL above,
  `OPPER_API_KEY`) and `MergedWith`.
- `internal/llm/provider/provider.go`: `case "opper"` and the error
  message. `prompts.go`: `opper` maps to `ProfileFrontier`.
- Guide, `tools_core.go` assist description, `docs/llm.md`,
  `README.md`, `SECURITY.md` and `CLAUDE.md` list the provider.
- Tests: `opper_test.go` (missing key and model, json_schema request,
  base URL path, freeform, API error, reasoning_effort), config,
  provider, prompts and guide marker tests.

The embedding API key fallback from the Requesty change is left out:
`embedOpenAI` appends `/v1/embeddings` to a base that does not end in
`/v1`, which would not match Opper's `/v3/compat/embeddings`.

Not run locally: `go test -race ./...` and `make lint` (no Go
toolchain on this machine). Pool ids checked against Opper's public
catalogue (`GET https://api.opper.ai/v3/models`).

Disclosure: Opper is my startup.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@zzet

zzet commented Oct 26, 2026

Copy link
Copy Markdown
Owner

Thanks for the PR, and for covering the /v3/compat path, structured requests and reasoning effort in the tests.

Please rework this as a gateway preset. We made that architectural change in #929, following the same discussion on #928. Requesty is now a preset, and Cheaper Inference and API Route use that path too. The Requesty built-in that this branch follows is no longer the pattern for adding gateways.

The new opper.New constructor delegates to openaicompat.Client with a Bearer key. The existing custom-provider factory already handles that, preserves the base URL's path when appending /chat/completions, and supports json_schema, reasoning_effort and prompt_profile: frontier. Opper's Chat Completions reference documents that endpoint and authentication scheme. This can use the shared implementation without adding another provider package, reserved name, config block and set of factory/prompt switches.

Users can configure Opper on current main already, using the default model proposed here:

gortex provider add opper \
  --base-url https://api.opper.ai/v3/compat \
  --model gpt-5.4-mini \
  --api-key-env OPPER_API_KEY \
  --prompt-profile frontier

Then set llm.provider: opper. Alternatively, use an inline entry:

llm:
  provider: opper
  custom:
    opper:
      base_url: https://api.opper.ai/v3/compat
      model: gpt-5.4-mini
      api_key_env: OPPER_API_KEY
      schema_mode: json_schema
      prompt_profile: frontier

GORTEX_LLM_MODEL and GORTEX_LLM_EFFORT already apply to custom providers and presets. The shared client will call /v3/compat/chat/completions; it does not insert /v1.

To ship Opper as a preset, the core change is three files:

  1. Add an entry to gatewayPresets in internal/llm/presets.go:

    "opper": {
        BaseURL:       "https://api.opper.ai/v3/compat",
        Model:         "gpt-5.4-mini",
        APIKeyEnv:     "OPPER_API_KEY",
        SchemaMode:    "json_schema",
        PromptProfile: "frontier",
    },
  2. Extend TestConfig_ActiveCustom_PresetResolution in internal/llm/presets_test.go to check that Config{Provider: "opper"} resolves without registration, with the expected URL, model, key env, schema mode and frontier prompt tier.

  3. Add a row to the Gateway presets table in docs/llm.md.

That gives users llm.provider: opper with no registration step, while allowing their own opper entry to override the preset.

Please drop the new internal/llm/provider/opper package, llm.opper config/default/env/merge wiring, reserved-name and factory cases, prompt-profile case, and the provider-list/guide/MCP-description edits. The shared implementation already owns those behaviours. If retaining the /v3/compat request-path regression test, put it alongside the existing custom-provider tests in internal/llm/provider/provider_custom_test.go.

There are also merge conflicts. I checked head 06c13bf2 against main at c7abb9b9; GitHub reports CONFLICTING, and a local merge calculation confirms conflicts in:

docs/llm.md
internal/llm/config.go
internal/llm/config_test.go
internal/llm/prompts.go
internal/llm/prompts_test.go
internal/llm/provider.go
internal/llm/provider/provider.go

Starting from current main and applying the small preset change is likely easier than resolving these conflicts around the old built-in wiring. Please run the LLM tests and the repository's pinned lint/build checks after reworking it. The current tests use mock HTTP servers; a short live check of strict json_schema and reasoning_effort with the proposed default model would also help confirm gateway compatibility.

The policy is documented in Adding a provider: new wire protocols or authentication schemes need a built-in provider; OpenAI-compatible gateways use custom providers or presets.

Felixkw12 and others added 2 commits October 30, 2026 17:27
Rework per review: start from main and add Opper to gatewayPresets
instead of a built-in provider package. The preset uses the shared
custom-provider client at https://api.opper.ai/v3/compat with
OPPER_API_KEY, json_schema and the frontier prompt tier. Adds the preset
resolution check, a Gateway presets table row, and a regression test
that a base URL path other than /v1 is kept when /chat/completions is
appended. The earlier opper package and its config, factory and prompt
wiring are dropped.

Disclosure: Opper is my startup.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A live check of both structured-output modes through the preset passed
with gpt-6.1-terra. With gpt-5.4-mini the non-strict tool-call schema is
rejected by Opper's route for that model today.

Disclosure: Opper is my startup.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Felixkw12

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review. Reworked as a gateway preset from current main, following #929:

  • internal/llm/presets.go: an opper entry (https://api.opper.ai/v3/compat, OPPER_API_KEY, json_schema, frontier)
  • internal/llm/presets_test.go: the opper case in TestConfig_ActiveCustom_PresetResolution
  • docs/llm.md: a row in the Gateway presets table
  • internal/llm/provider/provider_custom_test.go: TestNew_CustomBaseURLPathIsKept, the /v3/compat path regression test, next to the other custom provider tests

The provider package and its config, factory, prompt and docs wiring are gone, so the diff against main is those four files. I merged main into the branch rather than force pushing.

Live check. I ran the preset through the shared client against Opper with GORTEX_LLM_EFFORT=low, using a temporary go test behind a build tag on a GitHub Actions runner (Go 1.27.3). It is not part of this PR. It sends one strict json_schema request (the expand terms shape) and one non-strict request (the tool call shape).

That check changed the default model. With gpt-5.4-mini the strict shape passed, but the tool call shape got a 400 because Opper's route for that model currently validates the non-strict schema as strict. That is a bug on our side and I am raising it with our platform team. The same two requests passed on gpt-6.1-terra, claude-haiku-5-5, gemini-3.8-flash and deepseek-v4.1-flash, so the preset now defaults to gpt-6.1-terra.

At the PR head (417ee7e) with the preset default:

resolved: base_url=https://api.opper.ai/v3/compat model=gpt-6.1-terra schema_mode=json_schema prompt_profile=frontier reasoning_effort=low
strict json_schema reply: {"terms":["graph traversal","graph search","breadth-first search","BFS","depth-first search","DFS", ...]}
tool-call reply: {"args":{},"tool":"search_symbols"}
--- PASS: TestLiveOpperPreset (5.05s)

I did not run the repository's lint and build locally; CI on this PR covers them.

@zzet
zzet merged commit 9988ecd into zzet:main Oct 31, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants