Conversation
added 3 commits
September 13, 2026 22:07
Opencode is OpenAI-compatible but requires an x-opencode-session header on every call. Add OpencodeProvider (subclass of OpenAICompatibleProvider) that generates a random, stable-per-conversation id and sends it on chat, model-listing and context-detection requests. Add optional extra_headers support to OpenAICompatibleProvider and ContextDetector, then wire the provider through the factory, config, API routes, handlers, translate/refine pipelines, CLI, frontend UI and all 7 locales. Add unit tests covering the header behaviour.
B1: allow OPENCODE_API_KEY/OPENCODE_MODEL through /api/settings by lifting the allow-list to a module-level frozenset (previously the UI silently dropped the key and model on save). M1: forward context_window/log_callback in the create_llm_client opencode branch, matching the openai branch, so the EPUB/refine paths keep the job's context size and log callback. m1: apply extra_headers before the base headers so a caller-supplied dict cannot clobber Content-Type or Authorization (openai provider + context detector). m2: stop showing the DeepSeek 'using fallback list' wording for Opencode, which has no fallback list. m3/m4: document that OPENCODE_API_ENDPOINT is read at startup, and fail fast when no model is configured instead of minting an unusable provider. n1-n3: canonical key placeholders, restore TROUBLESHOOTING indentation and correct the cloud-endpoint comparison. Add regression tests for the settings allow-list, kwargs forwarding, header precedence, context-detection probe, factory guards/endpoint-ignore, model listing edge cases and the absence of default pricing.
The provider was missing from several enumerations: the README logo row, the .env.example and docker-compose option lists, the API-key rotation syntax list, the CLI example set, the Docker provider list, the translator skill, and a few code docstrings/comments that enumerate providers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add Opencode provider (OpenAI-compatible + mandatory session header)
Summary
Adds an Opencode LLM provider. The endpoint is fully OpenAI-compatible
(
chat/completions,models), with one requirement: every request mustcarry an
x-opencode-sessionheader holding a stable id for the conversation.The provider is wired end-to-end: adapter, factory, config/
.env, web API,CLI, frontend UI and all 7 UI locales.
Motivation
Opencode rejects calls that omit
x-opencode-session, so it cannot be usedthrough the generic
openaiprovider. Rather than special-case it at each callsite, this introduces a thin
OpenAICompatibleProvidersubclass that owns theheader, plus a small
extra_headersextension point on the base provider.Design
One provider instance is created per translation job and reused for every
chunk (see
GenericTranslator.translate→LLMClient), so the session id isminted once at provider construction and reused for the whole job. Parallel
workers and retries share it; a separate job (or the auto-prep/sample probe)
gets its own.
opencode-session-. It is neverderived from the machine, the install or user data — this keeps the existing
install-fingerprint regression guard meaningful.
completions, model listing, and context-detection probes — and is set
per-request, never on the shared
httpxclient defaults.OPENCODE_API_ENDPOINT(read atserver startup). The per-request
llm_api_endpointfield is ignored, like theother cloud providers, so a stale Ollama value in the UI can't hijack it.
get_default_pricingreturnsNoneand the cost estimator degrades to "noestimate". A test pins this as intentional.
What changed
Provider core
src/core/llm/providers/opencode.py(new):OpencodeProvidersubclass;generates/reuses the session id; implements
get_available_models().src/core/llm/providers/openai.py: optionalextra_headersonOpenAICompatibleProvider, merged into every request (baseContent-Type/Authorizationwin over caller-supplied extras).src/core/llm/utils/context_detection.py: optionalextra_headersso thecontext probe carries the session too.
Registration & config
src/core/llm/factory.py:opencodebranch (key + model required; explicitsession_id/conversation_idpassthrough).src/core/llm/__init__.py,src/core/llm_client.py: export / client wiring.src/config.py:OPENCODE_API_KEY,OPENCODE_MODEL,OPENCODE_API_ENDPOINT,TranslationConfigfields.src/api/api_keys.py:OPENCODE_API_KEYenv mapping..env.example: Opencode block.API & CLI
src/api/blueprints/config_routes.py:/api/modelslisting,/api/configmasks,/api/settingsGET flag, and the/api/settingsPOSTallow-list (see review fixes).
src/api/blueprints/translation_routes.py,src/api/handlers.py: keyplumbing through every pipeline (txt/srt/epub/docx + refine).
src/core/adapters/{translate_file,refine_file}.py,src/core/translator.py,src/core/epub/translator.py,src/core/refine/*: forwardopencode_api_key.translate.py:--provider opencode,--opencode_api_key,OPENCODE_MODELdefault, and fail-fast guards for a missing key/model.
Frontend & i18n
settings persistence and provider labels.
settings:*keys + 1errors:*key, added to all 7 locales(
en,fr,es,de,zh-CN,ja,ko) —test_frontend_i18n.pygreen.Docs
docs/PROVIDERS.md(setup),docs/CLI.md,docs/TROUBLESHOOTING.md,docs/API_KEY_ROTATION.md,README.md.Tests
tests/unit/test_opencode_provider.py: header present/stable/distinct, not onthe shared client, UUIDv4/machine-independence, header precedence,
context-probe header, model-listing parse + edge cases, factory guards and
endpoint-ignore.
tests/unit/test_opencode_integration.py: settings allow-list + endpoint notUI-editable,
create_llm_clientkwargs forwarding, intentional no-pricing.Review follow-ups (commit
9f8cf48)An independent review requested changes; all findings are addressed:
/api/settingssilently droppedOPENCODE_API_KEY/OPENCODE_MODEL; added to the allow-list (lifted to a module-level constantso it is regression-tested).
create_llm_client('opencode', ...)droppedcontext_window/log_callback; now forwarded like theopenaibranch.extra_headerscould clobberContent-Type/Authorization; baseheaders now win.
Testing
python3 -m venv venv && ./venv/bin/pip install -r requirements-dev.txt ./venv/bin/python -m pytest tests/unit/test_opencode_provider.py tests/unit/test_opencode_integration.py tests/test_frontend_i18n.py -q ./venv/bin/python -m pytest -qorigin/main(0a26cec) inthis environment and unrelated to this PR:
tests/characterization/test_progress_baseline.py(stale progressgoldens),
tests/unit/test_path_validator.py::test_reject_path_separator_backslash(platform-specific).
No new failures were introduced.
Notes for reviewers
x-opencode-sessionis notX-Session-Token/X-Client-Agent/X-Request-ID;tests/unit/test_no_install_fingerprint.pystill passes and the shared clientdefaults are unchanged.
refineruns create a new client, hence a new session id — a distinctconversation, which is the intended semantics.
OPENCODE_API_ENDPOINTis notUI-editable (consistent with other cloud endpoints).
Checklist