snapshot before v0.5 refactor

This commit is contained in:
kai
2026-04-21 12:31:58 +08:00
commit 4a38f6bed1
82 changed files with 15230 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
# Activate Deep Research environment (venv + secrets)
# Usage: cd <project-root> && source scripts/activate.sh
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
echo "ERROR: use 'source', not 'bash':"
echo " source scripts/activate.sh"
exit 1
fi
# Project root = directory containing pyproject.toml
# Walk up from $PWD until we find it (or give up at /)
_find_root() {
local dir="${PWD}"
while [[ "${dir}" != "/" ]]; do
[[ -f "${dir}/pyproject.toml" ]] && echo "${dir}" && return 0
dir="$(dirname "${dir}")"
done
return 1
}
_DR_ROOT="$(_find_root 2>/dev/null)"
if [[ -z "${_DR_ROOT}" ]]; then
echo "ERROR: pyproject.toml not found in ${PWD} or any parent."
echo " cd to the project root first, then: source scripts/activate.sh"
return 1
fi
# activate venv
if [[ -f "${_DR_ROOT}/.venv/bin/activate" ]]; then
source "${_DR_ROOT}/.venv/bin/activate"
echo "venv: active ($(python --version 2>&1))"
else
echo "venv: not found - run: bash ${_DR_ROOT}/scripts/setup.sh"
fi
# load secrets
if [[ -f "${_DR_ROOT}/secrets.env" ]]; then
set -a
source "${_DR_ROOT}/secrets.env"
set +a
if [[ -n "${ZENMUX_API_KEY:-}" ]]; then
echo "secrets: loaded (ZENMUX_API_KEY=${ZENMUX_API_KEY:0:12}...)"
else
echo "secrets: loaded but ZENMUX_API_KEY is empty"
fi
else
echo "secrets: missing - run: cp secrets.env.example secrets.env"
fi
# npm proxy fix: unset empty npm_config_proxy so npm inherits system http_proxy
# without this, opencode crashes with "proxy.url must be a non-empty string"
# when dynamically loading npm packages like @ai-sdk/anthropic
if [[ -z "${npm_config_proxy:-}" ]]; then
unset npm_config_proxy
fi
if [[ -z "${npm_config_https_proxy:-}" ]]; then
unset npm_config_https_proxy
fi
unset -f _find_root
unset _DR_ROOT
+147
View File
@@ -0,0 +1,147 @@
#!/usr/bin/env bash
# Deep Research - Environment setup (macOS + Debian/Ubuntu, using uv)
# Usage: bash scripts/setup.sh
set -euo pipefail
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
RST='\033[0m'
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
cd "${PROJECT_ROOT}"
echo -e "${CYAN}============================================${RST}"
echo -e "${CYAN} Deep Research - Setup (uv)${RST}"
echo -e "${CYAN} Project root: ${PROJECT_ROOT}${RST}"
echo -e "${CYAN}============================================${RST}"
# -------- OS detection --------
OS="unknown"
case "$(uname -s)" in
Darwin) OS="macos" ;;
Linux)
if [[ -f /etc/debian_version ]]; then
OS="debian"
elif [[ -f /etc/redhat-release ]]; then
OS="rhel"
else
OS="linux"
fi
;;
esac
echo -e "${YELLOW}Detected OS: ${OS}${RST}"
# -------- [1/4] uv check / install --------
echo ""
echo -e "${CYAN}[1/4] Checking uv...${RST}"
if ! command -v uv >/dev/null 2>&1; then
echo -e " ${YELLOW}uv not found. Installing...${RST}"
echo -e " ${YELLOW}(uv = fast Python package manager written in Rust, by Astral)${RST}"
echo ""
read -r -p " Auto-install uv? [Y/n] " REPLY
echo ""
if [[ ! ${REPLY} =~ ^[Nn]$ ]]; then
curl -LsSf https://astral.sh/uv/install.sh | sh
# bring uv into current PATH
[[ -f "${HOME}/.local/bin/uv" ]] && export PATH="${HOME}/.local/bin:${PATH}"
if ! command -v uv >/dev/null 2>&1; then
echo -e " ${RED}ERROR: uv still not found after install.${RST}"
echo " Add ~/.local/bin to PATH, then re-run:"
echo " export PATH=\"\${HOME}/.local/bin:\${PATH}\""
exit 1
fi
else
echo -e " ${RED}Manual install:${RST}"
echo " macOS: brew install uv"
echo " all: curl -LsSf https://astral.sh/uv/install.sh | sh"
echo " pip: pip install --user uv"
exit 1
fi
fi
UV_VER="$(uv --version 2>&1 | head -1)"
echo -e " ${GREEN}OK: ${UV_VER}${RST}"
# -------- [2/4] Python info --------
echo ""
echo -e "${CYAN}[2/4] Python check...${RST}"
if command -v python3 >/dev/null 2>&1; then
SYS_PY="$(python3 --version 2>&1)"
echo -e " ${GREEN}System: ${SYS_PY} (uv manages its own Python 3.10+ automatically)${RST}"
else
echo -e " ${YELLOW}python3 not found in PATH (uv will download Python automatically)${RST}"
fi
# -------- [3/4] uv sync --------
echo ""
echo -e "${CYAN}[3/4] Syncing Python dependencies (uv sync)...${RST}"
echo -e " ${YELLOW}First run may take a moment to download Python + packages.${RST}"
uv sync
echo -e " ${GREEN}OK: .venv/ ready${RST}"
# -------- [4/4] System binaries --------
echo ""
echo -e "${CYAN}[4/4] Checking system binaries...${RST}"
check_bin() {
local name="${1}"
local install_macos="${2}"
local install_debian="${3}"
if command -v "${name}" >/dev/null 2>&1; then
local ver
ver="$("${name}" --version 2>&1 | head -1)"
echo -e " ${GREEN}OK: ${name} - ${ver}${RST}"
else
echo -e " ${YELLOW}MISSING: ${name}${RST}"
case "${OS}" in
macos) echo " Install: ${install_macos}" ;;
debian) echo " Install: ${install_debian}" ;;
*) echo " Install ${name} for your OS" ;;
esac
fi
}
check_bin "pandoc" "brew install pandoc" "sudo apt install pandoc"
check_bin "opencode" "curl -fsSL https://opencode.ai/install | bash" "curl -fsSL https://opencode.ai/install | bash"
check_bin "curl" "pre-installed" "sudo apt install curl"
# -------- Summary --------
echo ""
echo -e "${CYAN}============================================${RST}"
echo -e "${CYAN} Setup complete${RST}"
echo -e "${CYAN}============================================${RST}"
echo ""
if [[ -f secrets.env ]]; then
echo -e "${GREEN}OK: secrets.env found${RST}"
else
echo -e "${YELLOW}TODO: create secrets.env${RST}"
echo " cp secrets.env.example secrets.env"
echo " # fill in ZENMUX_API_KEY etc."
fi
FONT_COUNT="$(find .opencode/templates/fonts -maxdepth 1 \( -name "*.otf" -o -name "*.ttf" \) 2>/dev/null | wc -l | tr -d ' ')"
if [[ "${FONT_COUNT}" -ge 7 ]]; then
echo -e "${GREEN}OK: fonts ready (${FONT_COUNT} files)${RST}"
else
echo -e "${YELLOW}TODO: download fonts (${FONT_COUNT}/7 present)${RST}"
echo " bash .opencode/templates/fonts/download-fonts.sh"
fi
echo ""
echo "Next steps:"
echo " 1. source scripts/activate.sh"
echo " 2. bash .opencode/templates/fonts/download-fonts.sh"
echo " 3. bash scripts/verify-zenmux.sh"
echo " 4. opencode"
echo ""
echo "Tips:"
echo " Run a script directly: uv run python .opencode/templates/report-template.py ..."
echo " Add a package: uv add <package>"
echo " Upgrade all: uv lock --upgrade && uv sync"
echo " Full rebuild: rm -rf .venv && bash scripts/setup.sh"
+113
View File
@@ -0,0 +1,113 @@
#!/usr/bin/env bash
# 验证 zenmux 两个端点 + prompt cache 是否可用
# 用法:bash scripts/verify-zenmux.sh
set -euo pipefail
# 加载密钥
if [[ -f secrets.env ]]; then
set -a; source secrets.env; set +a
fi
if [[ -z "${ZENMUX_API_KEY:-}" ]]; then
echo "❌ ZENMUX_API_KEY 未设置。请 cp secrets.env.example secrets.env 并填入 key"
exit 1
fi
echo "============================================"
echo " zenmux 端点与 prompt cache 自检"
echo "============================================"
# -------- 测试 1OpenAI 兼容端点(Gemini --------
echo ""
echo "[1/3] 测试 OpenAI 兼容端点(google/gemini-2.5-pro,隐式缓存)..."
RESP1=$(curl -sS -w "\n---HTTP:%{http_code}---\n" -X POST "https://zenmux.ai/api/v1/chat/completions" \
-H "Authorization: Bearer $ZENMUX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/gemini-2.5-pro",
"messages": [{"role": "user", "content": "用一句话回答:PubMed 是什么?"}],
"max_tokens": 100
}')
echo "$RESP1" | tail -5
echo ""
# -------- 测试 2Anthropic 兼容端点(Claude Haiku --------
echo "[2/3] 测试 Anthropic 兼容端点(claude-haiku-4.5..."
RESP2=$(curl -sS -w "\n---HTTP:%{http_code}---\n" -X POST "https://zenmux.ai/api/anthropic/v1/messages" \
-H "x-api-key: $ZENMUX_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-haiku-4.5",
"max_tokens": 100,
"messages": [{"role": "user", "content": "用一句话回答:PubMed 是什么?"}]
}')
echo "$RESP2" | tail -5
echo ""
# -------- 测试 3Claude + prompt cacheSonnet --------
echo "[3/3] 测试 Claude prompt cache(连续 2 次请求,第 2 次应命中 cache..."
# 构造一个至少 1024 tokens 的 system prompt(约 3000 字符中文足够)
LONG_PROMPT=$(printf 'You are a biomedical research assistant. ' && \
for i in $(seq 1 80); do
printf 'This is test content line %d to build cacheable prefix context. ' "$i"
done)
PAYLOAD=$(cat <<EOF
{
"model": "claude-haiku-4.5",
"max_tokens": 50,
"system": [
{
"type": "text",
"text": "${LONG_PROMPT}",
"cache_control": {"type": "ephemeral"}
}
],
"messages": [{"role": "user", "content": "Say OK."}]
}
EOF
)
echo " 第 1 次调用(建 cache..."
R1=$(curl -sS -X POST "https://zenmux.ai/api/anthropic/v1/messages" \
-H "x-api-key: $ZENMUX_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d "$PAYLOAD")
CACHE_CREATE=$(echo "$R1" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('usage',{}).get('cache_creation_input_tokens', 0))" 2>/dev/null || echo "?")
echo " cache_creation_input_tokens = $CACHE_CREATE"
sleep 2
echo " 第 2 次调用(应命中 cache..."
R2=$(curl -sS -X POST "https://zenmux.ai/api/anthropic/v1/messages" \
-H "x-api-key: $ZENMUX_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d "$PAYLOAD")
CACHE_READ=$(echo "$R2" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('usage',{}).get('cache_read_input_tokens', 0))" 2>/dev/null || echo "?")
echo " cache_read_input_tokens = $CACHE_READ"
echo ""
echo "============================================"
echo " 结果"
echo "============================================"
if [[ "$CACHE_CREATE" != "0" && "$CACHE_CREATE" != "?" ]]; then
echo "✅ Cache 写入成功($CACHE_CREATE tokens"
else
echo "⚠️ Cache 未写入(可能是 prompt 太短或端点不支持)"
fi
if [[ "$CACHE_READ" != "0" && "$CACHE_READ" != "?" ]]; then
echo "✅ Cache 命中成功($CACHE_READ tokens)— 成本大幅降低"
else
echo "⚠️ Cache 未命中(检查 AGENTS.md §6.5 诊断步骤)"
fi
echo ""
echo "完整 usage 字段(第 2 次):"
echo "$R2" | python3 -m json.tool 2>/dev/null | grep -A 10 '"usage"' || echo "$R2"