Skip to content

feat(llm): add OpenCode Go as a managed provider (chat-only, no Responses/CountTokens) - #933

Open
mkurkar wants to merge 2 commits into
mattermost:masterfrom
mkurkar:feat/opencodego-provider
Open

feat(llm): add OpenCode Go as a managed provider (chat-only, no Responses/CountTokens)#933
mkurkar wants to merge 2 commits into
mattermost:masterfrom
mkurkar:feat/opencodego-provider

Conversation

@mkurkar

@mkurkar mkurkar commented Jul 27, 2026

Copy link
Copy Markdown

Summary

Adds OpenCode Go as a first-class managed provider behind the existing OpenAI-compatible plumbing, registering it with a custom-provider slot whose AllowedRequests gate refuses every endpoint the gateway does not implement.
Default model is kimi-k3.

Problem

OpenCode Go is an OpenAI-compatible inference gateway that exposes only /v1/chat/completions under https://opencode.ai/zen/go/v1 (Bearer auth).
Routing the current openaicompatible config through Bifrost produced two
hard failures, both of which crashed the parser or the gateway:

  1. 404 HTML on token preflight. CountTokens always posts a Responses-API request body to /v1/responses/input_tokens, which the gateway does not serve. The 404 HTML response page is not JSON; Bifrost's parser crashes.
  2. 400 on injected OpenAI metadata. When CountTokens succeeded (it did not, see (1)), Bifrost would inject reasoning.effort into the body for reasoning-model names, which the gateway rejects as invalid_request_error per its standard chat-completions contract.

The chat stream itself works once both issues are bypassed. The gateway's 16 model IDs are lowercase-hyphenated (kimi-k3, grok-4.5, glm-5.2,qwen3.7-max, deepseek-v4-pro, etc.); not the upstream-native names.

Approach

Mirror the existing chat-only fallback wrap, but apply it on the primary path so /v1/responses* is blocked there too:

  1. Register opencodego as a new ServiceType in the openAICompatibleProviders registry. Default model: kimi-k3.
  2. Custom-provider wrap on primary (bifrost/bifrost.go::New): when cfg.Provider == schemas.OpenAI && !cfg.UseResponsesAPI, the primary is registered under a custom name (openai::primary) with chatOnly = true.
    GetConfigForProvider then attaches AllowedRequests{ChatCompletion: true, ChatCompletionStream: true}
    which (per schemas.AllowedRequests semantics) implicitly refuses every other operation including CountTokens. Bifrost returns unsupported_operation synchronously; the plugin maps that to
    llm.ErrUnsupportedTokenCount, which falls through to llm.EstimateTokens.
  3. Route through the wrapped slot. b.provider now stores the registered name (primaryEntry.registeredName()) instead of the bare base type. As a bonus this lands on Bifrost's default: arm in vendored.../providers/openai/chat.go (which skips normalizeReasoningEffort), doubling the chat-path protection.
  4. One source of truth for managed URLs. Extracted
    applyProviderURLDefaults so the primary path (new) and the serviceConfigToFallbackEntry path both inject
    https://opencode.ai/zen/go/v1 for opencodego, https://api.cohere.ai/compatibility/v1 for cohere, and
    https://api.mistral.ai/v1 for mistral. normalizeOpenAIBaseURL then strips the trailing /v1 so Bifrost can prepend /v1/chat/completions.
  5. Native tools excluded. SupportsNativeTools(ServiceTypeOpenCodeGo)
    returns false explicitly even though the base type is schemas.OpenAI, the gateway does not implement native tools. Request-time filtering
    already short-circuits, but the public predicate stays accurate for built-in-tool fallback gating.
  6. Transcriber switch is left alone in bots/bots.go OpenCode Go
    cannot serve /v1/audio/transcriptions, so it cannot be the transcript generator. Existing allowlist (OpenAI / OpenAI-Compatible / Azure) is intentionally narrower than the chat provider list.

Files changed

File Change
llm/service_types.go New ServiceTypeOpenCodeGo = "opencodego".
llm/providers.go Registry entry with DefaultModel: "kimi-k3".
llm/configuration.go IsValidService requires APIKey only; URL defaults.
bifrost/config.go MapServiceTypeToProviderschemas.OpenAI, IsSupported adds opencodego, applyProviderURLDefaults helper, SupportsNativeTools excludes opencodego.
bifrost/bifrost.go Primary-wrap block; LLM.account field added for tests; provider carries registeredName().
webapp/src/components/system_console/service.tsx Dropdown option, display-name map, isOpenAIType; API URL and Org ID fields stay hidden (matches mistral UX).

Tests

  • bifrost/bifrost_test.go
    • New TestNew_OpenCodeGoPrimaryIsWrappedAsChatOnlyCustom — asserts
      b.provider == "openai::primary", the bare schemas.OpenAI slot is
      not registered, and AllowedRequests rejects
      Responses/ResponsesStream/CountTokens while allowing
      ChatCompletion/ChatCompletionStream.
    • New TestNew_DirectOpenAIAndAzurePrimariesRemainStandard — guards
      the inverse: real OpenAI/Azure primaries keep the bare base-type slot,
      not a custom slot.
    • Extended TestProviderAccount_ChatOnlyCustomConfig with
      AllowedRequests.CountTokens assertion.
  • bifrost/config_test.go — opencodego entries for SupportsNativeTools
    and FilterNativeToolsForServiceType.
  • llm/providers_test.go, llm/configuration_test.go — registry and
    IsValidService cases.
  • bifrost/bifrost_test.go::TestCountTokensOmitsMaxOutputTokens updated:
    synthetic Provider: schemas.OpenAI, UseResponsesAPI: false configs are
    no longer reachable from NewFromServiceConfig (which forces true),
    so the test now uses the production value to keep exercising the
    body-shape assertion instead of the chat-only gate.

All ./llm/... ./bifrost/... ./bots/... ./config/... ./api/... tests pass.

Risk & follow-ups

  • Behavior expansion. The wrap condition is cfg.Provider == schemas.OpenAI && !cfg.UseResponsesAPI, which also covers openaicompatible with the operator toggle off. That is arguably the right behavior (same gateway pitfall) and is consistent with how the fallback path already worked, but it is a behavior change for any openaicompatible bot created before this PR. Worth a CHANGELOG entry.
  • No model auto-discovery. supportsModelFetching in webapp/src/components/system_console/service.tsx does not include
    opencodego, so admins must type the model name. OpenCode Go does serve a /v1/models listing — adding it would be a one-liner behind a feature flag if desired.
  • Documentation. docs/providers.md should gain a short OpenCode Go section. Out of scope here; happy to follow up.
  • Transcriber parity. If/when OpenCode Go adds /v1/audio/transcriptions, the switch in bots/bots.go::GetTranscribe can grow a case.

Verification

  • go test ./llm/... ./bifrost/... ./bots/... ./config/... ./api/...
    green on Go 1.26.5.
  • Bundle built: make dist produces a 113 MB tar.gz with five
    cross-compiled server binaries plus the webapp bundle that contains the
    new "OpenCode Go" dropdown entry.

Screenshot

image

Summary by CodeRabbit

  • New Features

    • Added support for configuring and using the OpenCode Go service.
    • Added OpenCode Go to the service selection menu with appropriate defaults, validation, and display labels.
    • OpenCode Go supports chat completions and streaming while excluding unsupported native tools and request types.
    • Improved provider diagnostics and standardized service URL handling.
  • Bug Fixes

    • Prevented unsupported requests from being sent to chat-only providers.

Adds support for OpenCode Go (https://opencode.ai/zen/go/v1), an
OpenAI-compatible inference gateway that only serves /v1/chat/completions
and rejects the auxiliary routes Bifrost normally pings.

Default model is kimi-k3 (one of the 16 IDs the gateway actually serves —
Grok 4.5, GLM-5.2, GLM-5.1, Kimi K3, Kimi K2.7 Code, Kimi K2.6,
MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.7 Max, Qwen3.7 Plus, Qwen3.6 Plus,
MiniMax M2.7, MiniMax M3, DeepSeek V4 Pro, DeepSeek V4 Flash, Hy3).

Backend (llm, bifrost):
  * llm/service_types.go: register ServiceTypeOpenCodeGo = "opencodego".
  * llm/providers.go: add opencodego to the OpenAI-compatible registry
    with DefaultModel = "kimi-k3".
  * llm/configuration.go: validate via APIKey only (URL auto-defaults).
  * bifrost/config.go: map opencodego to schemas.OpenAI, register it
    with IsSupported, extract applyProviderURLDefaults as a single
    source of truth shared by primary and fallback paths, and exclude
    opencodego from SupportsNativeTools (the gateway doesn't speak
    web_search).
  * bifrost/bifrost.go: wrap an OpenAI-base primary that does not speak
    the Responses API (opencodego, openaicompatible-with-Responses-off)
    under its own custom-provider slot 'openai::primary' with chatOnly
    set. GetConfigForProvider attaches AllowedRequests with only
    ChatCompletion / ChatCompletionStream = true, which implicitly
    refuses POSTs to /v1/responses and /v1/responses/input_tokens.
    Bifrost returns 'unsupported_operation' for CountTokens, which the
    plugin maps to llm.ErrUnsupportedTokenCount → llm.EstimateTokens
    fallback. b.provider now carries the registered name so
    req.Provider = b.provider targets the wrapped slot; this also lands
    on Bifrost's default chat arm (skipping normalizeReasoningEffort).

Tests:
  * bifrost/bifrost_test.go: new TestNew_OpenCodeGoPrimaryIsWrapped
    AsChatOnlyCustom, TestNew_DirectOpenAIAndAzurePrimariesRemainStandard;
    extend TestProviderAccount_ChatOnlyCustomConfig with CountTokens.
  * bifrost/config_test.go: opencodego entries for SupportsNativeTools
    and FilterNativeTools.
  * llm/providers_test.go, llm/configuration_test.go: opencodego
    registry and IsValidService cases.
  * bifrost/bifrost_test.go: TestCountTokensOmitsMaxOutputTokens now
    sets UseResponsesAPI:true to reflect the production config for
    direct OpenAI (where the chat-only wrap does not fire).

Webapp (System Console):
  * webapp/src/components/system_console/service.tsx: add 'opencodego'
    to the service-type dropdown, the display-name map ('OpenCode Go'),
    and isOpenAIType. API URL and Organization ID fields stay hidden
    (server-side defaults applied to URL; the gateway has no org
    concept).

Docs: docs/providers.md would benefit from a follow-up entry for
OpenCode Go but is not part of this commit.
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 2187b2e0-4871-4f26-ab5e-8984c45bd03f

📥 Commits

Reviewing files that changed from the base of the PR and between 775bdb8 and a7c3150.

📒 Files selected for processing (2)
  • webapp/src/components/system_console/service.tsx
  • webapp/src/i18n/en.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • webapp/src/components/system_console/service.tsx

📝 Walkthrough

Walkthrough

Adds OpenCode Go as a chat-only OpenAI-compatible service, including validation, provider registration, URL defaults, native-tool filtering, Bifrost wrapping, tests, and system console support.

Changes

OpenCode Go integration

Layer / File(s) Summary
Service type and provider contract
llm/service_types.go, llm/configuration.go, llm/providers.go, llm/*_test.go
Defines ServiceTypeOpenCodeGo, requires an API key, registers the kimi-k3 default model, and validates provider configuration.
Configuration routing and capability rules
bifrost/config.go, bifrost/config_test.go
Maps OpenCode Go to OpenAI, applies its default base URL, marks it unsupported for native tools, and includes it among supported services.
Chat-only Bifrost registration
bifrost/bifrost.go, bifrost/bifrost_test.go
Wraps non-Responses OpenAI-base primaries as chat-only custom providers, records the resolved account, and verifies fallback and direct-provider behaviour.
Service console configuration
webapp/src/components/system_console/service.tsx, webapp/src/i18n/en.json
Adds OpenCode Go to the service selector and display mapping, treats it as OpenAI-like, and hides organization or account identifier fields.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Admin
  participant ServiceConsole
  participant ServiceConfig
  participant Bifrost
  participant OpenCodeGo
  Admin->>ServiceConsole: select OpenCode Go and enter API key
  ServiceConsole->>ServiceConfig: submit service configuration
  ServiceConfig->>Bifrost: map provider and apply default URL
  Bifrost->>Bifrost: register chat-only custom provider
  Bifrost->>OpenCodeGo: send chat completion request
Loading

Suggested labels: Setup Cloud Test Server

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: adding OpenCode Go as a managed provider with chat-only restrictions and no Responses or CountTokens support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@webapp/src/components/system_console/service.tsx`:
- Line 52: Update the service display-map entry and dropdown fallback for the
“opencodego” service to use intl.formatMessage(...) with an extracted message
instead of the hardcoded “OpenCode Go” string. Ensure both user-facing paths
reuse the i18n-derived label and remove the hardcoded values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 05d3ae02-8ec2-4213-a886-12e70e203d13

📥 Commits

Reviewing files that changed from the base of the PR and between aaa3e4f and 775bdb8.

📒 Files selected for processing (10)
  • bifrost/bifrost.go
  • bifrost/bifrost_test.go
  • bifrost/config.go
  • bifrost/config_test.go
  • llm/configuration.go
  • llm/configuration_test.go
  • llm/providers.go
  • llm/providers_test.go
  • llm/service_types.go
  • webapp/src/components/system_console/service.tsx

Comment thread webapp/src/components/system_console/service.tsx Outdated
The 'OpenCode Go' display label was hardcoded in two places — the
mapServiceTypeToDisplayName entry and the dropdown fallback — so
formatjs extract would miss it on regen and the label would never
be localizable. Follow the existing scaleAIToDisplayName pattern:
extract a dedicated helper that calls intl.formatMessage, and drop
the hardcoded map entry + dropdown fallback in favor of the helper.

webapp/src/i18n/en.json is regenerated by 'make i18n-extract' to
include the new '4WZ6dc/F' message key.
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.

1 participant