Skip to content

Instantly share code, notes, and snippets.

@linyanm
Last active August 12, 2026 03:49
Show Gist options
  • Select an option

  • Save linyanm/a2a4a3912acc91aa3bee3f37006eecad to your computer and use it in GitHub Desktop.

Select an option

Save linyanm/a2a4a3912acc91aa3bee3f37006eecad to your computer and use it in GitHub Desktop.
Claude Code VS Code + OpenCode Go + DeepSeek V4 Flash (one-click install)

Claude Code (VS Code) + OpenCode Go + DeepSeek V4 Flash

Gist: https://gist.github.com/linyanm/a2a4a3912acc91aa3bee3f37006eecad

在最新版 VS Code Claude Code 插件 中,通过 OpenCode Go 使用 DeepSeek V4 Flash(含 Anthropic Messages / 原生 web_search)。

实测版本:Claude Code VS Code 2.1.224,OpenCode Go https://opencode.ai/zen/go


一键安装

bash <(curl -fsSL https://gist.githubusercontent.com/linyanm/a2a4a3912acc91aa3bee3f37006eecad/raw/install-opencode-go-claude.sh)

脚本会:

  1. 提示输入 OpenCode Go API Key(sk-...,输入时不回显)
  2. 写入 ~/.claude/bin/claude-vscode-wrapper(绕过 VS Code 入口限制)
  3. 更新 VS Code User settings.json
  4. 更新 ~/.claude/settings.jsonenv / model
  5. 可选:探测 deepseek-v4-flash 是否可用

安装后请 Reload Window,并 新开一个 Claude 会话


为什么不能只改 ANTHROPIC_BASE_URL

踩过三个坑:

1. Base URL 不要带 /v1

Claude Code 会请求:

${ANTHROPIC_BASE_URL}/v1/messages
配置 实际请求 结果
https://opencode.ai/zen/go/v1 .../zen/go/v1/v1/messages ❌ 404
https://opencode.ai/zen/go .../zen/go/v1/messages ✅ 200

2. 必须用 ANTHROPIC_API_KEY,不要用 ANTHROPIC_AUTH_TOKEN

环境变量 发出的 header OpenCode Go /messages
ANTHROPIC_API_KEY x-api-key
ANTHROPIC_AUTH_TOKEN Authorization: Bearer 401 Missing API key

3. VS Code 入口会忽略第三方 API Key(关键)

扩展强制设置:

CLAUDE_CODE_ENTRYPOINT=claude-vscode

在该入口下,Claude 会忽略 ANTHROPIC_API_KEY,表现为:

[Bootstrap] Skipped: no usable OAuth, WIF, or API key
Could not resolve authentication method
→ 弹出 “How do you want to log in?”

解决办法:用 claudeCode.claudeProcessWrapper 在启动前改写入口:

export CLAUDE_CODE_ENTRYPOINT=sdk-ts
exec "$@"

手动配置(等价于脚本)

1) Wrapper

~/.claude/bin/claude-vscode-wrapper

#!/bin/bash
export CLAUDE_CODE_ENTRYPOINT=sdk-ts
if [[ -n "${ANTHROPIC_AUTH_TOKEN:-}" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
  export ANTHROPIC_API_KEY="$ANTHROPIC_AUTH_TOKEN"
fi
exec "$@"
chmod +x ~/.claude/bin/claude-vscode-wrapper

2) VS Code User settings.json

{
  "claudeCode.claudeProcessWrapper": "/Users/你的用户名/.claude/bin/claude-vscode-wrapper",
  "claudeCode.disableLoginPrompt": true,
  "claudeCode.environmentVariables": [
    { "name": "ANTHROPIC_BASE_URL", "value": "https://opencode.ai/zen/go" },
    { "name": "ANTHROPIC_API_KEY", "value": "sk-你的OpenCodeGoKey" },
    { "name": "CLAUDE_CODE_SKIP_AUTH_LOGIN", "value": "1" },
    { "name": "ANTHROPIC_MODEL", "value": "deepseek-v4-flash" },
    { "name": "ANTHROPIC_DEFAULT_OPUS_MODEL", "value": "deepseek-v4-flash" },
    { "name": "ANTHROPIC_DEFAULT_SONNET_MODEL", "value": "deepseek-v4-flash" },
    { "name": "ANTHROPIC_DEFAULT_HAIKU_MODEL", "value": "deepseek-v4-flash" },
    { "name": "CLAUDE_CODE_SUBAGENT_MODEL", "value": "deepseek-v4-flash" }
  ]
}

Windows / Linux 把 wrapper 路径改成自己的 home 即可。

3) ~/.claude/settings.json(推荐同步写一份)

{
  "model": "deepseek-v4-flash",
  "env": {
    "ANTHROPIC_BASE_URL": "https://opencode.ai/zen/go",
    "ANTHROPIC_API_KEY": "sk-你的OpenCodeGoKey",
    "CLAUDE_CODE_SKIP_AUTH_LOGIN": "1",
    "ANTHROPIC_MODEL": "deepseek-v4-flash",
    "ANTHROPIC_DEFAULT_OPUS_MODEL": "deepseek-v4-flash",
    "ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-v4-flash",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "deepseek-v4-flash",
    "CLAUDE_CODE_SUBAGENT_MODEL": "deepseek-v4-flash"
  }
}

OpenCode Go 端点说明

官方文档把 DeepSeek 标在 Chat Completions:

https://opencode.ai/zen/go/v1/chat/completions

Messages 协议实测可用

POST https://opencode.ai/zen/go/v1/messages
Header: x-api-key: sk-...
Header: anthropic-version: 2023-06-01

原生搜索需要声明工具(Claude Code 会自己带):

"tools": [{ "type": "web_search_20250305", "name": "web_search" }]

成功时响应会出现 server_tool_use + web_search_tool_result


验证

curl

curl -sS https://opencode.ai/zen/go/v1/messages \
  -H "x-api-key: $OPENCODE_GO_KEY" \
  -H "content-type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "deepseek-v4-flash",
    "max_tokens": 64,
    "messages": [{"role":"user","content":"只回复:ok"}]
  }'

VS Code

  1. Command Palette → Developer: Reload Window
  2. 新开 Claude 会话
  3. 发送「你好」
  4. 不要点 Claude.ai / Console 登录

Output → Claude VSCode 正常时不应再出现:

  • Missing API key
  • Could not resolve authentication method
  • 登录页

卸载 / 恢复

# 删除 wrapper
rm -f ~/.claude/bin/claude-vscode-wrapper

# 手动从 VS Code settings.json 删除:
# - claudeCode.claudeProcessWrapper
# - claudeCode.environmentVariables 中的 ANTHROPIC_* 项
# - 或把 claudeCode.disableLoginPrompt 改回 false

# ~/.claude/settings.json 删除 env 字段,model 改回你原来的

脚本安装时会备份:

  • ~/Library/Application Support/Code/User/settings.json.bak-opencode-go-*(macOS)
  • ~/.config/Code/User/settings.json.bak-opencode-go-*(Linux)
  • ~/.claude/settings.json.bak-opencode-go-*

参考


License

MIT。按需自用,Key 请勿提交到公开仓库。

#!/usr/bin/env bash
# ============================================================
# install-opencode-go-claude.sh
#
# 一键把 VS Code Claude Code 插件接到 OpenCode Go 的
# deepseek-v4-flash(Anthropic Messages 协议)。
#
# 用法:
# bash install-opencode-go-claude.sh
# bash install-opencode-go-claude.sh --key 'sk-xxx'
# OPENCODE_GO_API_KEY=sk-xxx bash install-opencode-go-claude.sh
#
# 可选参数:
# --key KEY OpenCode Go API Key
# --model NAME 默认 deepseek-v4-flash
# --base-url URL 默认 https://opencode.ai/zen/go
# --skip-probe 跳过 API 探测
# --uninstall 尽量撤销本脚本写入的配置
# -h, --help 帮助
# ============================================================
set -euo pipefail
MODEL="deepseek-v4-flash"
BASE_URL="https://opencode.ai/zen/go"
API_KEY="${OPENCODE_GO_API_KEY:-}"
SKIP_PROBE=0
UNINSTALL=0
R=$'\033[31m'; G=$'\033[32m'; Y=$'\033[33m'; B=$'\033[1m'; N=$'\033[0m'
info() { printf '%b\n' "${G}$*${N}"; }
warn() { printf '%b\n' "${Y}$*${N}"; }
err() { printf '%b\n' "${R}$*${N}"; }
die() { err "$*"; exit 1; }
usage() {
sed -n '2,20p' "$0" | sed 's/^# \?//'
exit 0
}
while [ $# -gt 0 ]; do
case "$1" in
--key) API_KEY="${2:-}"; shift 2 ;;
--model) MODEL="${2:-}"; shift 2 ;;
--base-url) BASE_URL="${2:-}"; shift 2 ;;
--skip-probe) SKIP_PROBE=1; shift ;;
--uninstall) UNINSTALL=1; shift ;;
-h|--help) usage ;;
*) die "未知参数: $1(用 --help 查看用法)" ;;
esac
done
# ---------- paths ----------
HOME_DIR="${HOME}"
WRAPPER_DIR="${HOME_DIR}/.claude/bin"
WRAPPER_PATH="${WRAPPER_DIR}/claude-vscode-wrapper"
CLAUDE_SETTINGS="${HOME_DIR}/.claude/settings.json"
TS="$(date +%Y%m%d-%H%M%S)"
detect_vscode_settings() {
local candidates=()
case "$(uname -s)" in
Darwin)
candidates+=(
"${HOME_DIR}/Library/Application Support/Code/User/settings.json"
"${HOME_DIR}/Library/Application Support/Code - Insiders/User/settings.json"
"${HOME_DIR}/Library/Application Support/Cursor/User/settings.json"
"${HOME_DIR}/Library/Application Support/VSCodium/User/settings.json"
)
;;
Linux)
candidates+=(
"${HOME_DIR}/.config/Code/User/settings.json"
"${HOME_DIR}/.config/Code - Insiders/User/settings.json"
"${HOME_DIR}/.config/Cursor/User/settings.json"
"${HOME_DIR}/.config/VSCodium/User/settings.json"
)
;;
MINGW*|MSYS*|CYGWIN*|Windows_NT)
if [ -n "${APPDATA:-}" ]; then
candidates+=(
"${APPDATA}/Code/User/settings.json"
"${APPDATA}/Code - Insiders/User/settings.json"
"${APPDATA}/Cursor/User/settings.json"
)
fi
;;
esac
local p
for p in "${candidates[@]}"; do
if [ -f "$p" ]; then
echo "$p"
return 0
fi
done
# default create location
case "$(uname -s)" in
Darwin) echo "${HOME_DIR}/Library/Application Support/Code/User/settings.json" ;;
*) echo "${HOME_DIR}/.config/Code/User/settings.json" ;;
esac
}
VS_SETTINGS="$(detect_vscode_settings)"
read_key_hidden() {
local prompt="$1" val=""
if [ -r /dev/tty ]; then
printf '%s' "$prompt" > /dev/tty
# shellcheck disable=SC2162
IFS= read -r -s val < /dev/tty || true
printf '\n' > /dev/tty
else
printf '%s' "$prompt"
# shellcheck disable=SC2162
IFS= read -r -s val || true
printf '\n'
fi
printf '%s' "$val"
}
need_python() {
command -v python3 >/dev/null 2>&1 || die "需要 python3"
}
backup_file() {
local f="$1"
if [ -f "$f" ]; then
cp -p "$f" "${f}.bak-opencode-go-${TS}"
info "已备份: ${f}.bak-opencode-go-${TS}"
fi
}
write_wrapper() {
mkdir -p "$WRAPPER_DIR"
cat > "$WRAPPER_PATH" <<'EOF'
#!/bin/bash
# Claude VS Code forces CLAUDE_CODE_ENTRYPOINT=claude-vscode, which ignores
# third-party ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN. Rewrite to sdk-ts so
# OpenCode Go / DeepSeek Anthropic-compatible endpoints work.
export CLAUDE_CODE_ENTRYPOINT=sdk-ts
# OpenCode Go /messages only accepts x-api-key (from ANTHROPIC_API_KEY)
if [[ -n "${ANTHROPIC_AUTH_TOKEN:-}" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
export ANTHROPIC_API_KEY="$ANTHROPIC_AUTH_TOKEN"
fi
exec "$@"
EOF
chmod +x "$WRAPPER_PATH"
info "已写入 wrapper: $WRAPPER_PATH"
}
probe_api() {
local key="$1"
command -v curl >/dev/null 2>&1 || { warn "未找到 curl,跳过探测"; return 0; }
info "正在探测 ${BASE_URL}/v1/messages + ${MODEL} ..."
local code body
body="$(mktemp)"
code="$(
curl -sS -o "$body" -w '%{http_code}' \
-H "x-api-key: ${key}" \
-H 'content-type: application/json' \
-H 'anthropic-version: 2023-06-01' \
"${BASE_URL}/v1/messages" \
-d "{\"model\":\"${MODEL}\",\"max_tokens\":32,\"messages\":[{\"role\":\"user\",\"content\":\"Reply with exactly: ok\"}]}" \
--connect-timeout 15 --max-time 60 || true
)"
if [ "$code" = "200" ]; then
info "✅ API 探测成功 (HTTP 200)"
python3 - "$body" <<'PY' 2>/dev/null || true
import json,sys
d=json.load(open(sys.argv[1]))
texts=[c.get("text","") for c in d.get("content",[]) if c.get("type")=="text"]
print("model:", d.get("model"), "| text:", " ".join(texts)[:80])
PY
else
warn "⚠️ API 探测失败 HTTP ${code}"
head -c 400 "$body" 2>/dev/null; echo
warn "请检查 API Key / 网络 / 模型名。配置仍会写入,你可稍后重试。"
fi
rm -f "$body"
}
patch_vscode_settings() {
local settings="$1" wrapper="$2" key="$3" model="$4" base="$5"
mkdir -p "$(dirname "$settings")"
if [ ! -f "$settings" ]; then
printf '{\n}\n' > "$settings"
fi
backup_file "$settings"
python3 - "$settings" "$wrapper" "$key" "$model" "$base" <<'PY'
import json, re, sys
from pathlib import Path
path, wrapper, key, model, base = sys.argv[1:6]
raw = Path(path).read_text(encoding="utf-8")
def strip_jsonc(s: str) -> str:
out = []
i, n = 0, len(s)
in_str = esc = False
while i < n:
c = s[i]
if in_str:
out.append(c)
if esc:
esc = False
elif c == "\\":
esc = True
elif c == '"':
in_str = False
i += 1
continue
if c == '"':
in_str = True
out.append(c)
i += 1
continue
if c == "/" and i + 1 < n and s[i + 1] == "/":
while i < n and s[i] != "\n":
i += 1
continue
if c == "/" and i + 1 < n and s[i + 1] == "*":
i += 2
while i + 1 < n and not (s[i] == "*" and s[i + 1] == "/"):
i += 1
i += 2
continue
out.append(c)
i += 1
s2 = "".join(out)
s2 = re.sub(r",\s*([}\]])", r"\1", s2)
return s2
try:
data = json.loads(strip_jsonc(raw))
except Exception as e:
raise SystemExit(f"无法解析 VS Code settings.json: {e}")
if not isinstance(data, dict):
raise SystemExit("settings.json 根节点必须是 object")
data["claudeCode.claudeProcessWrapper"] = wrapper
data["claudeCode.disableLoginPrompt"] = True
env_names = {
"ANTHROPIC_BASE_URL": base,
"ANTHROPIC_API_KEY": key,
"CLAUDE_CODE_SKIP_AUTH_LOGIN": "1",
"ANTHROPIC_MODEL": model,
"ANTHROPIC_DEFAULT_OPUS_MODEL": model,
"ANTHROPIC_DEFAULT_SONNET_MODEL": model,
"ANTHROPIC_DEFAULT_HAIKU_MODEL": model,
"CLAUDE_CODE_SUBAGENT_MODEL": model,
}
existing = data.get("claudeCode.environmentVariables", [])
if isinstance(existing, dict):
# rare alternate shape
items = [{"name": k, "value": str(v)} for k, v in existing.items()]
elif isinstance(existing, list):
items = [x for x in existing if isinstance(x, dict) and isinstance(x.get("name"), str)]
else:
items = []
# remove old ANTHROPIC_AUTH_TOKEN to avoid Bearer auth on Go
drop = set(env_names) | {"ANTHROPIC_AUTH_TOKEN"}
items = [x for x in items if x.get("name") not in drop]
for name, value in env_names.items():
items.append({"name": name, "value": value})
data["claudeCode.environmentVariables"] = items
# Write pretty JSON (comments in original settings will be dropped)
Path(path).write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(f"updated {path}")
PY
}
patch_claude_settings() {
local settings="$1" key="$2" model="$3" base="$4"
mkdir -p "$(dirname "$settings")"
if [ ! -f "$settings" ]; then
printf '{\n}\n' > "$settings"
fi
backup_file "$settings"
python3 - "$settings" "$key" "$model" "$base" <<'PY'
import json, sys
from pathlib import Path
path, key, model, base = sys.argv[1:5]
try:
data = json.loads(Path(path).read_text(encoding="utf-8"))
except Exception:
data = {}
if not isinstance(data, dict):
data = {}
data["model"] = model
env = data.get("env") if isinstance(data.get("env"), dict) else {}
env.update({
"ANTHROPIC_BASE_URL": base,
"ANTHROPIC_API_KEY": key,
"CLAUDE_CODE_SKIP_AUTH_LOGIN": "1",
"ANTHROPIC_MODEL": model,
"ANTHROPIC_DEFAULT_OPUS_MODEL": model,
"ANTHROPIC_DEFAULT_SONNET_MODEL": model,
"ANTHROPIC_DEFAULT_HAIKU_MODEL": model,
"CLAUDE_CODE_SUBAGENT_MODEL": model,
})
# remove auth token to avoid confusion
env.pop("ANTHROPIC_AUTH_TOKEN", None)
data["env"] = env
Path(path).write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(f"updated {path}")
PY
}
uninstall_all() {
warn "卸载:移除 wrapper,并清理本脚本写入的 VS Code / Claude 配置键"
rm -f "$WRAPPER_PATH"
info "已删除 $WRAPPER_PATH(若存在)"
if [ -f "$VS_SETTINGS" ]; then
backup_file "$VS_SETTINGS"
python3 - "$VS_SETTINGS" <<'PY'
import json, re, sys
from pathlib import Path
path = sys.argv[1]
raw = Path(path).read_text(encoding="utf-8")
def strip_jsonc(s: str) -> str:
out=[]; i=0; n=len(s); in_str=esc=False
while i<n:
c=s[i]
if in_str:
out.append(c)
if esc: esc=False
elif c=='\\': esc=True
elif c=='"': in_str=False
i+=1; continue
if c=='"':
in_str=True; out.append(c); i+=1; continue
if c=='/' and i+1<n and s[i+1]=='/':
while i<n and s[i]!='\n': i+=1
continue
if c=='/' and i+1<n and s[i+1]=='*':
i+=2
while i+1<n and not (s[i]=='*' and s[i+1]=='/'): i+=1
i+=2; continue
out.append(c); i+=1
return re.sub(r',\s*([}\]])', r'\1', ''.join(out))
data=json.loads(strip_jsonc(raw))
data.pop('claudeCode.claudeProcessWrapper', None)
data.pop('claudeCode.disableLoginPrompt', None)
names={
'ANTHROPIC_BASE_URL','ANTHROPIC_API_KEY','ANTHROPIC_AUTH_TOKEN',
'CLAUDE_CODE_SKIP_AUTH_LOGIN','ANTHROPIC_MODEL',
'ANTHROPIC_DEFAULT_OPUS_MODEL','ANTHROPIC_DEFAULT_SONNET_MODEL',
'ANTHROPIC_DEFAULT_HAIKU_MODEL','CLAUDE_CODE_SUBAGENT_MODEL'
}
items=data.get('claudeCode.environmentVariables')
if isinstance(items, list):
data['claudeCode.environmentVariables']=[
x for x in items
if not (isinstance(x, dict) and x.get('name') in names)
]
if not data['claudeCode.environmentVariables']:
data.pop('claudeCode.environmentVariables', None)
Path(path).write_text(json.dumps(data, ensure_ascii=False, indent=2)+'\n', encoding='utf-8')
print('cleaned', path)
PY
fi
if [ -f "$CLAUDE_SETTINGS" ]; then
backup_file "$CLAUDE_SETTINGS"
python3 - "$CLAUDE_SETTINGS" <<'PY'
import json, sys
from pathlib import Path
path=sys.argv[1]
data=json.loads(Path(path).read_text(encoding='utf-8'))
data.pop('env', None)
# don't force-remove model; user may still want it
Path(path).write_text(json.dumps(data, ensure_ascii=False, indent=2)+'\n', encoding='utf-8')
print('cleaned env from', path)
PY
fi
info "✅ 卸载完成。请 Reload VS Code Window。"
exit 0
}
# ---------- main ----------
need_python
if [ "$UNINSTALL" -eq 1 ]; then
uninstall_all
fi
printf '%b\n' "${B}OpenCode Go → Claude Code (VS Code) 安装器${N}"
echo "Base URL : $BASE_URL"
echo "Model : $MODEL"
echo "VS Code : $VS_SETTINGS"
echo "Claude : $CLAUDE_SETTINGS"
echo
if [ -z "$API_KEY" ]; then
API_KEY="$(read_key_hidden "请输入 OpenCode Go API Key (sk-...): ")"
fi
API_KEY="$(printf '%s' "$API_KEY" | tr -d '[:space:]')"
[ -n "$API_KEY" ] || die "API Key 为空"
case "$API_KEY" in
sk-*) ;;
*) warn "Key 不是以 sk- 开头,仍继续(若失败请检查)" ;;
esac
if [ "$SKIP_PROBE" -eq 0 ]; then
probe_api "$API_KEY"
fi
write_wrapper
patch_vscode_settings "$VS_SETTINGS" "$WRAPPER_PATH" "$API_KEY" "$MODEL" "$BASE_URL"
patch_claude_settings "$CLAUDE_SETTINGS" "$API_KEY" "$MODEL" "$BASE_URL"
cat <<EOF
${G}${B}✅ 安装完成${N}
接下来:
1. VS Code 执行:Developer: Reload Window
2. 新开一个 Claude 会话(不要点 Claude.ai / Console 登录)
3. 发送「你好」测试
关键配置:
- wrapper : ${WRAPPER_PATH}
- VS Code : claudeCode.claudeProcessWrapper + environmentVariables
- Claude : ~/.claude/settings.json 的 env/model
卸载:
bash $0 --uninstall
说明文档见同 gist 的 README.md
EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment