Add opencode provider support alongside Claude Code settings#1
Conversation
olist-code only patched ~/.claude/settings.json; it now also writes the olist-ai-gateway as a custom OpenAI-compatible provider in ~/.config/opencode/opencode.json, so opencode users get the gateway without manual setup.
7a04c30 to
163979b
Compare
| CONFIG_DIR = Path.home() / ".olist-code-adapter" | ||
| CONFIG_FILE = CONFIG_DIR / "config.json" | ||
| CLAUDE_SETTINGS_FILE = Path.home() / ".claude" / "settings.json" | ||
| OPENCODE_SETTINGS_FILE = Path.home() / ".config" / "opencode" / "opencode.json" |
There was a problem hiding this comment.
Boa pegada — confirmei rodando com opencode de verdade (instalei localmente pra testar). Agora o código detecta um opencode.jsonc já existente e atualiza ele em vez de criar um opencode.json do lado. Ressalva: os comentários do arquivo se perdem na reescrita (pra preservar precisaria de uma lib JSONC-aware, tipo commentjson, que não temos como dependência hoje — se topar, adiciono). Validei com o binário real: opencode models reconhece seu provider + o nosso, ambos no mesmo .jsonc.
| existing["provider"] = {} | ||
|
|
||
| models: dict[str, Any] = {config.models.opus: {}} | ||
| if config.models.sonnet: |
There was a problem hiding this comment.
Estranho, o que isso tem esse modelo tem a ver com o opencode, o fluxo melhor seria você perguntar no CLI o harness do usuário, caso seja o opencode você nao precisa adaptar a configuração do claude code para o opencode.
There was a problem hiding this comment.
Faz sentido, corrigido. init agora pergunta qual harness (claude/opencode/both) e run aceita --harness; só escrevemos o(s) settings do harness escolhido. Testei os 3 casos com o CLI real: harness=claude não cria nada em ~/.config/opencode, harness=opencode não cria ~/.claude, e both mantém o comportamento anterior.
Two issues from review: - olist-code always wrote both Claude Code and opencode settings, even for users who only use one of the two harnesses. init now asks (and run accepts --harness), and only the selected harness's settings get touched. - If the user already has an opencode.jsonc (JSONC, comments allowed) instead of opencode.json, we were blind to it and created a separate opencode.json next to it. Now we detect and update the existing .jsonc in place instead of creating a duplicate file. Comments in that file are not preserved across the edit (would need a JSONC-preserving writer/dependency) — known trade-off. Verified against the real opencode binary (not just unit tests): confirmed provider recognition via 'opencode models' for the plain .json path, the existing-.jsonc path, and each harness selection.
Gustavo's follow-up point: the Opus/Sonnet/Haiku framing is Anthropic-specific and meaningless when the user only configures opencode (which just needs a flat list of model ids from the provider, no tier concept). init now asks which harness first, and only uses Opus/Sonnet/Haiku wording when Claude Code is involved; opencode-only picks a generic 'primeiro/segundo/terceiro modelo' prompt instead.
…lArts Two issues surfaced while testing this branch against a real OpenAI-compatible upstream (Huawei ModelArts, GLM 5.2), both upstream of the opencode work but blocking end-to-end validation: - The non-streaming message/delta key bug (already fixed on main via a separate PR) wasn't present on this branch since it forked before that fix landed — cherry-picked here so this branch is testable on its own. - New: anthropic_to_openai() sent both max_tokens and max_completion_tokens with the same value. Huawei's API (and presumably other strict OpenAI-compatible servers) rejects requests that set both, since max_tokens is the deprecated alias for max_completion_tokens. Now only max_completion_tokens is sent.
Correction: sending only max_completion_tokens is not actually the safer default. Found a documented case (langchain-ai/langchain#29954) of self-hosted vLLM deployments rejecting max_completion_tokens outright ('Extra inputs are not permitted'), since older/vanilla OpenAI-compatible servers only implement the original max_tokens field. Verified max_tokens alone also works fine against the real Huawei ModelArts endpoint (GLM 5.2) that motivated this fix. max_completion_tokens is only required by OpenAI's own o1/reasoning models, not this project's target providers.
olist-code's primary/intended target is always the olist-ai-gateway, which already dedupes this exact case before forwarding upstream (apps/gateway/services/proxy.py: drops max_tokens when max_completion_tokens is also present). Keeping equivalent logic here duplicates a responsibility that belongs to the gateway — the layer that actually knows per-provider quirks — for no current concrete need.
* Add opencode provider support alongside Claude Code settings olist-code only patched ~/.claude/settings.json; it now also writes the olist-ai-gateway as a custom OpenAI-compatible provider in ~/.config/opencode/opencode.json, so opencode users get the gateway without manual setup. * Address review: ask which harness, respect existing opencode.jsonc Two issues from review: - olist-code always wrote both Claude Code and opencode settings, even for users who only use one of the two harnesses. init now asks (and run accepts --harness), and only the selected harness's settings get touched. - If the user already has an opencode.jsonc (JSONC, comments allowed) instead of opencode.json, we were blind to it and created a separate opencode.json next to it. Now we detect and update the existing .jsonc in place instead of creating a duplicate file. Comments in that file are not preserved across the edit (would need a JSONC-preserving writer/dependency) — known trade-off. Verified against the real opencode binary (not just unit tests): confirmed provider recognition via 'opencode models' for the plain .json path, the existing-.jsonc path, and each harness selection. * Ask harness before model names, use generic wording for opencode Gustavo's follow-up point: the Opus/Sonnet/Haiku framing is Anthropic-specific and meaningless when the user only configures opencode (which just needs a flat list of model ids from the provider, no tier concept). init now asks which harness first, and only uses Opus/Sonnet/Haiku wording when Claude Code is involved; opencode-only picks a generic 'primeiro/segundo/terceiro modelo' prompt instead. * Bring in real-gateway bugfixes needed to validate against Huawei ModelArts Two issues surfaced while testing this branch against a real OpenAI-compatible upstream (Huawei ModelArts, GLM 5.2), both upstream of the opencode work but blocking end-to-end validation: - The non-streaming message/delta key bug (already fixed on main via a separate PR) wasn't present on this branch since it forked before that fix landed — cherry-picked here so this branch is testable on its own. - New: anthropic_to_openai() sent both max_tokens and max_completion_tokens with the same value. Huawei's API (and presumably other strict OpenAI-compatible servers) rejects requests that set both, since max_tokens is the deprecated alias for max_completion_tokens. Now only max_completion_tokens is sent. * Send only max_tokens instead of max_completion_tokens Correction: sending only max_completion_tokens is not actually the safer default. Found a documented case (langchain-ai/langchain#29954) of self-hosted vLLM deployments rejecting max_completion_tokens outright ('Extra inputs are not permitted'), since older/vanilla OpenAI-compatible servers only implement the original max_tokens field. Verified max_tokens alone also works fine against the real Huawei ModelArts endpoint (GLM 5.2) that motivated this fix. max_completion_tokens is only required by OpenAI's own o1/reasoning models, not this project's target providers. * Revert max_tokens/max_completion_tokens dedup change olist-code's primary/intended target is always the olist-ai-gateway, which already dedupes this exact case before forwarding upstream (apps/gateway/services/proxy.py: drops max_tokens when max_completion_tokens is also present). Keeping equivalent logic here duplicates a responsibility that belongs to the gateway — the layer that actually knows per-provider quirks — for no current concrete need.

Summary
olist-codecurrently only patches~/.claude/settings.jsonto point Claude Code at the local proxy. It now also writes/merges a custom OpenAI-compatible provider entry (olist-ai-gateway) into~/.config/opencode/(opencode.jsonor an existingopencode.jsonc), following opencode's documented provider schema (@ai-sdk/openai-compatible,options.baseURL/apiKey,models).olist-ai-gatewayprovider entry is added/overwritten.initnow asks which harness the user runs (claude/opencode/both) — asked right after login, before the model prompts — and only that harness's settings get written.opencode-only skips the Anthropic-specific "Opus/Sonnet/Haiku" wording (meaningless for opencode) in favor of a generic model-name prompt.runaccepts--harnessfor non-interactive use..jsoncsupport (from review): if the user already has~/.config/opencode/opencode.jsonc(opencode supports both formats), we update it in place instead of creating a separateopencode.jsonnext to it. Known trade-off: comments in that file are stripped on write (would need a JSONC-preserving library to keep them — not added since there's no dependency for it today).content—openai_to_anthropic_response()read the reply fromchoices[0].delta, but non-streaming responses put it underchoices[0].message(deltais streaming-only). Found while validating this against a real gateway and a real opencode install; not opencode-specific, but needed for end-to-end testing.Note: a
max_tokens/max_completion_tokensdedup fix inadapter.pywas tried and then reverted —olist-ai-gatewayalready dedupes this exact case before forwarding upstream, so duplicating that logic inolist-code(which only ever talks to the gateway) was redundant.Test plan
pytest— full suite passes (60 passed), covering:update_claude_settings/update_opencode_settings(regression + new), harness dispatch (tests/test_cli.py),.jsoncdetection, and the non-streaming bugfix.ruff check/mypy— no new issues (one pre-existing unrelated line-length warning).opencode modelsfor the plain.jsonpath, the existing-.jsoncmerge path (both providers show up correctly), and each harness selection (claude-only doesn't touch opencode config,opencode-only doesn't touch~/.claude)./v1/messagespath (Claude Code) and the OpenAI-style/v1/chat/completionspath (opencode) — confirmed real model responses come back with correct, non-empty content after the bugfix.