Skip to content

feat(provider-opencode-go): add OpenCode Go Chat Completions provider worker - #1

Open
faramirezs wants to merge 7 commits into
mainfrom
feat/provider-opencode-go
Open

feat(provider-opencode-go): add OpenCode Go Chat Completions provider worker#1
faramirezs wants to merge 7 commits into
mainfrom
feat/provider-opencode-go

Conversation

@faramirezs

Copy link
Copy Markdown
Owner

What

New Rust worker provider-opencode-go: an LLM provider worker behind
llm-router speaking the OpenCode Go API — Chat Completions
(https://opencode.ai/zen/go/v1/chat/completions), SSE streaming, live model
discovery, auth/error taxonomy, reasoning-effort mapping, tool calling, and
structured output. Registers provider::opencode_go::stream / refresh_models
/ abort with the router, binds identity via registration token (state scope
provider-opencode-go), and reads OPENCODE_GO_API_KEY as credential.

Why

The stack has no way to route chat completions to an OpenCode Go subscription.
The OpenCode Go API is Chat Completions compatible, so the existing
provider-openai worker ports directly — same protocol, same relay/pump
scaffold, same error taxonomy, with the OpenAI-only surfaces (embeddings,
Responses API) dropped as dead code.

How it works

  • Credentials: resolved per request via router::provider::resolve
    (config slice → OPENCODE_GO_API_KEY env on the router → none); sent as
    Authorization: Bearer.
  • Wire / streaming: Chat Completions SSE → AssistantMessageEvent frames
    into a router-owned channel; ping ≥ every 30s of silence; a failed channel
    write (router::abort / caller gone) drops the SSE receiver and aborts the
    in-flight HTTP request. Stream-path tracing::debug for provider-side
    observability.
  • Catalog / discovery: live GET /v1/models supplies bare ids; each is
    enriched from a hardcoded curated metadata table (src/curated.rs) prepared
    from models.dev (2026-08-03) — context window, reasoning support/effort
    levels, tool-call and structured-output capability for the maintainer's
    curated model set (24 models.dev entries + hy3-preview). Ids outside the
    table keep conservative defaults (128K, no thinking, tools on) — same
    pattern as provider-openai's curated.rs.
  • Reasoning: thinking_level maps to the upstream reasoning_effort only
    when the model's curated effort list accepts the level (e.g. grok-4.5
    accepts low/medium/high; deepseek-v4-flash and glm-5.2 accept
    high/max; hy3 accepts none/low/high); toggle-only models and
    unknown ids stream without the field.
  • Registration: self-declares via router::provider::register with
    backoff until acked, re-declares on router::ready; registration_token
    persisted in iii-state (scope provider-opencode-go).

Scope / caveat

  • Port of provider-openai: mechanical renames only in most files; the
    OpenAI-only surface (ApiMode, embeddings, Responses-API event handlers and
    thinking deltas, curated reasoning-fallback ladder, luna guard) is dropped —
    OpenCode Go has no such surface.
  • Not a replacement or rewrite of the Node opencode/ worker —
    different role (CLI wrapper vs provider), no overlap; both install side by
    side.
  • hy3-preview is listed by the live GET /v1/models but the chat endpoint
    currently returns ModelNotFound — an upstream inconsistency; the curated
    row keeps conservative defaults and the provider surfaces the upstream error
    cleanly.
  • External contributor: no Linear ticket — no-ticket label applied.

Repo wiring

  • Root README.md Modules row added for provider-opencode-go
    (alphabetical, between provider-openai and provider-xai)
  • Release wiring: create-tag.yml, release.yml, alpha-release.yml, and
    .github/scripts/discover_changed_workers.py
  • llm-router/README.md reference note (same structure as provider-openai)

Verification

  • cargo fmt --check and cargo clippy --all-targets --all-features -- -D warnings clean
  • cargo test --all-features: 67 pass — 57 lib unit + 2 bin unit + 4
    schema/golden + 4 integration
  • Integration tests run against the real engine (v0.22.0) + llm-router with a
    stubbed upstream: chat stream end-to-end (incl. cache_read usage),
    401 → auth_expired error frame, refresh_models → catalog from the
    curated table, re-declare on router::ready
  • cargo build --release OK
  • Live smoke (real engine + llm-router 1.4.0 + OpenCode Go subscription):
    refresh_models → 25 models; router::models::list → 25 curated entries
    with metadata; router::complete served on glm-5, deepseek-v4-flash
    (incl. thinking_level: highreasoning_effort), and hy3

Test plan

  • cargo fmt --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test — 67 pass
  • III_ENGINE_BIN=$(which iii) cargo test --test integration -- --test-threads=1
  • Live: iii worker add provider-opencode-gorefresh_models
    router::models::listrouter::complete

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

skill-check — worker

0 verified, 54 skipped (no docs/).

Layer Result
structure
vale
render

Three for three. Nicely done.

OpenCode Go Chat Completions provider behind llm-router. Implements the
provider protocol: stream (SSE chunks to AssistantMessageEvent frames),
abort, refresh_models (live GET /v1/models enriched with models.dev
metadata: context window, reasoning efforts, tool/structured-output
capability), and re-declaration on router::ready. Chat Completions wire
format only, max_completion_tokens, strict json_schema structured output,
reasoning_effort low/medium/high for deepseek-/kimi-k2.7- families.

Wired into create-tag/release workflows and the harness worker deps.
README to the provider family structure (Behavior/Tests/Running), manifest
tags+description to the canonical form, release wiring in alpha-release.yml
and discover_changed_workers.py, llm-router README reference note.
Upstream MOT-4335 rewrote all provider identity prompts to teach the live
surface (register_trigger, harness::spawn, orchestrator: true) and stripped
orchestration-process doctrine. The fork-PR merge with the new main runs the
harness prompts sweep over every shipped prompt, which failed on our pre-
rewrite copy ("delegation is one-way" etc.). Absorb the rewritten prompt,
update the register.rs identity assertions, and apply the iii-state -> state
rename.
@faramirezs
faramirezs force-pushed the feat/provider-opencode-go branch from 099ac93 to 20201db Compare August 4, 2026 11:10
@faramirezs faramirezs added the no-ticket PR deliberately has no Linear ticket (bump/typo/CI-only) label Aug 4, 2026
- iii-permissions.yaml: deny provider::opencode_go::abort (agents must not
  cancel router-owned streams; matches provider-claude-code)
- sse.rs: relay delta.reasoning_content as thinking blocks (the OpenCode Go
  wire emits it, live-verified); bound tool-call index to 64 (malformed
  upstream could grow the vec unboundedly)
- upstream.rs: data_line per SSE spec — accept data: without a space and
  join repeated data: lines instead of silently dropping output
- curated.rs: per-model max_output_tokens from models.dev limit.output;
  4096 stays the unknown-id fallback
- reasoning.rs: Minimal maps to minimal then none (gpt-5.6-luna floor)
- register.rs: log router::ready trigger registration failures
- router_client.rs: narrow module doc claim
- README: repair split provider-openai table row; III_WS_URL -> III_URL
  (code + engine convention); thinking-delta relay note
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-ticket PR deliberately has no Linear ticket (bump/typo/CI-only)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant