Skip to content

Latest commit

 

History

History
98 lines (69 loc) · 22.2 KB

CHANGELOG.md

File metadata and controls

98 lines (69 loc) · 22.2 KB

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

分叉声明 / Fork notice:本项目自 TiaraBasori/opencode2api 的 v1.5.0 起由 samson910022/OpenCode2API 独立维护。v1.5.0(含)以前见上游历史; 此后变更见本节([Unreleased])及后续版本节。

Changed

  • 独立维护声明:README 致谢明确直接上游 TiaraBasori/opencode2api,LICENSE 追加上游归属,CHANGELOG 声明分叉点。
  • 仓库去耦合:文档克隆/Issue 链接与 Docker 镜像指向 samson910022/OpenCode2API,package.json 补全维护元数据。
  • Responses 示例模型:README、docs/api-reference.md、docs/getting-started.md 的 /v1/responses 示例统一为 opencode/muse-spark-1.3-contributor-free(解析逻辑无需改动,裸名与带前缀均兼容)。
  • SDK 对齐:@opencode-ai/sdk ^1.1.51 → ^1.18.29(与本地 server 1.18.29 对齐;v1 调用形状不变,零改码;139 tests 全绿;cross-spawn@7.0.6 为 SDK 新增 prod 依赖,既有包去 dev 标记,无新版本;tool.ids 仍走上游 experimental 路径,后续跟进)。
  • Unified env/file/default config merge (Phase 3A): index.ts and buildProxyConfig now share the strict resolvers in src/config/proxy-config.ts (resolve*Setting, parseStrictInteger/Port, parseHttpUrl, parseToolNameList); lookup stays env canonical > env legacy alias > file short key > hardcoded default with invalid values falling through. Library resolveDisableTools default is now true (was false), matching prod/docs; library consumers relying on the old tools-enabled default must now set DISABLE_TOOLS=false explicitly. Bool tokens are 1/true/yes/y/on vs 0/false/no/n/off everywhere. Docker/Compose keep empty defaults so code owns values; entrypoint.sh validates OPENCODE_PROXY_PORT/PORT and OPENCODE_SERVER_PORT against 1..65535 and unsets invalid proxy ports so config.json still applies.

Added

  • GitHub AI bot (gateway + CPA dual-channel): new github_bot/ (dual-provider llm_client with xhigh→high CPA mapping, verbatim thinking/signature passthrough, response_id session echo, _redact_secrets/sanitize_public_error_text; agent_orchestrator triage/review/explain/scan/fix-plan pipelines; repo_scan deterministic pre-scan; github_runner sticky-marker publish + allowlist labels + fingerprint-dedupe auto-issue + opt-in draft PRs) with role-separated primaries (triage gemini-3.8-flash-high, safety grok-4.6, api-compat claude-opus-4-6-thinking, docs muse-spark-1.3-contributor-free, explainer big-pickle, scanner grok-composer-2.5-fast) and cross-channel fallbacks; new workflows ai-review.yml / ai-scan.yml (weekly cron) / ai-fix.yml (/fix plans; draft PRs only with opt-in, human review required), each starting its own no-auth in-job gateway (API_KEY empty, OPENCODE_PROXY_MANAGE_BACKEND=true, anonymous out-of-box quota; external GATEWAY_BASE_URL secret overrides) with CPA as optional fallback; tests/test_ai_bot.py (34 tests); docs/AI_BOT.md. 新增 GitHub AI Bot(自託管 gateway 免費通道 + CPA 通道,定時掃描自動開 issue,/fix 規劃;opt-in 才開 draft PR,需人工審查)。
  • N×N protocol translator registry + route wiring: new pure src/converters/ matrix (registry/pipeline/formats/translator-types + 12 request + 12 response directed pairs for chat, responses, messages, interactions; stream + non-stream; fidelity.ts ledger with 3 full forward edges out of chat and 9 text-core legs; TokenCount intentionally unregistered so counting stays in the collector). Wiring: registry initializes once in createApp and is exposed via ctx.translators; src/converters/wire.ts Safe wrappers enforce error-envelope bypass (wrapped {error}, response.failed, error events, bare {message,type} bodies, raw SSE error strings) and per-stream holder reuse; POST /v1/messages inbound conversion now goes through the registry (thin wrapper over the same anthropic.ts pure layer, adaptive thinking collapsed to the legacy fallback for strict parity). Behavior is otherwise unchanged: wire/SSE/[DONE]/error-exit shapes stay route-owned, translated tools still flow through the external__* bridge + allowlist, translated usage is client-facing only. New src/converters/holder.ts + wire.ts, tests/translator-wire.test.js (fail-fast, bypass, holder, parity) and tests/translator-integration.test.js (live route outputs translate cross-protocol). 新增 N×N 纯函数转换矩阵与 messages 入站接线,线形与工具政策保持不变。
  • Interactions API (Gemini-compatible thin layer): new POST /v1beta/interactions (alias POST /v1/interactions) mapping text + google_search grounding onto the Responses pipeline, with system_instruction / previous_interaction_id / store:false (ephemeral) / SSE (interaction.created → step.delta → interaction.completed, no [DONE]). 新增 src/routes/interactions.ts 与 tests/interactions.test.js. Client-executed function tools are rejected with 400 (use /v1/responses for those).
  • Multi-key auth: API_KEY merges with OPENCODE_API_KEYS / API_KEYS (any match passes via timingSafeEqual; Bearer or x-api-key; empty means no auth). 新增 src/auth/keys.ts 与 tests/auth-multikey.test.js.
  • Free-limit fallback proxy pool: direct-only until a 429 free-limit error (FreeUsageLimitError / GoUsageLimitError) engages OPENCODE_UPSTREAM_PROXIES (failover-rr/round-robin alias / random, cooldown, socks-first, loopback bypass); stream paths degrade to poll while engaged; state exposed via /health/details + /metrics. 新增 src/upstream-proxy/ 与 tests/proxy-fallback*.test.js.
  • Server-side web_search grounding: /v1/responses tools: [{type: "web_search"}] (also web_search_preview / versioned web_search_* / google_search) drives the opencode built-in websearch and returns web_search_call + honest url_citation annotations only; web_search on chat/messages is rejected with 400 + pointer. 新增 src/search/grounding.ts 与 tests/responses-websearch.test.js / tests/hosted-search-routing.test.js.
  • Anthropic Messages API:新增 POST /v1/messages(max_tokens 必填,支持 system/tools(input_schema)/tool_choice{auto,any,tool,none}/thinking→reasoning/image;tool_use.id 原样往返;非流式回 message 对象,流式回 message_start/content_block_*/message_delta/message_stop 无 [DONE];认证同时支持 x-api-key;CORS 放行 x-api-key/anthropic-version)。新增 src/converters/anthropic.js 纯函数转换层与 tests/messages-anthropic.test.js(9 例)。附带修复 EXTERNAL_TOOL_PREFIX 缺 import 的 latent ReferenceError。
  • 官方对齐重试策略:移植上游 session/retry.ts(2s×2ⁿ⁻¹ 指数退避 +25% jitter;优先 retry-after-ms/retry-after,header 等待 clamp 30s;5xx 强制、isRetryable、ContextOverflow 永不)。重试次数 n 经 OPENCODE_PROXY_RETRY_MAX_RETRIES 控制(默认 3,总尝试 1+n,上限对齐官方 5)。chat 双路由与 messages 非串流改用新退避;responses 非串流新增同结构重试迴圈(仅无产出且 transient 时;responses/messages 串流保持单次尝试)。三处非串流的 prompt/poll 抛错同样走 transient 重试路由(传输层 throw 不再直接逃逸);真鉴权失败(invalid api key/unauthorized/authentication failed)立即透出不再烧退避。新增 src/retry/policy.js 与 tests/retry-policy.test.js(11 例)及接线/回归测试 14 例。

Fixed

  • Backend tool-hang + per-request dispose fixes: the proxy no longer PUT config {activeModel} on every request — native ConfigHttpApi.update disposes the whole backend project instance unconditionally (no changed-check), aborting in-flight sessions into the Aborted retry-loop storm; ensureActiveModel (src/backend/active-model.ts) PUTs only on actual model change per backend client (failures never cached, existing catch semantics preserved). Separately, the headless backend can no longer ask (nobody approves, so prompts hung until the 180s timeout) nor silently execute unlisted tools (the free-tier strip omits the prompt tools map, so the backend fell back to agent defaults = all on): backend opencode.json now carries a generated deny-all permission map (src/backend/backend-permission.ts, single source for entrypoint.sh via dist with static fallback and src/backend/manager.ts isolated path) allowing only explicitly configured internal tools, with external_directory always jailed to /home/node/project/**. 后端不再每请求 dispose(模型变更才 PUT activeModel);headless 后端不再 ask 卡死、不再静默执行未授权工具(deny-all,仅放行明确配置的 internal 工具)。Locked by tests/active-model.test.js + tests/backend-permission.test.js + tests/tool-protocol-parity.test.js.
  • Anonymous first-party backend posture + Zen free-tier gate handling: spawned backend pins OPENCODE_CLIENT to a known first-party value (cli default; desktop/acp/app pass through), strips the OPENCODE_API_KEY="public" footgun (anonymous uses empty), and git-inits the jail workspace best-effort (5s timeout, warn-and-continue) as defense-in-depth for session-dir sensitivity. Prompt tools maps have false entries stripped (true-only sent; all-false omitted) only for likely-free opencode models (-free suffix, big-pickle, union-alpha) to dodge the FreeTierError 403 (any false gates); stripped tools fall back to agent defaults server-side while paid/other providers keep the exact hard-disable map. The gate (...used from within OpenCode / FreeTierError) now surfaces as 403 permission_denied fail-fast (no retry, no proxy fallback) with false-positive guards for plain quota text. Jail stdout no longer prints absolute paths; interactions route gains text-level tool-markup stripping parity with chat/responses/messages. New tests/backend-identity.test.js / tests/first-party-gate.test.js / tests/prompt-tool-overrides.test.js / tests/session-ids.test.js. No new proxy knob (no config matrix change).
  • Translator parity fixes (N×N matrix review): chat array content parts no longer silently dropped (extract text/content); responses incomplete with content_filter reason round-trips instead of collapsing to length (non-stream + stream); is_error now round-trips via the ERROR: text prefix on chat/messages/responses tool legs (strict true/"true" check; success text literally starting with ERROR: round-trips as is_error by design); named custom tool declarations are kept so custom_tool_call invocations always have a matching declaration; ID generation centralized on makeId(prefix) (toolu_ fallback separator canonicalized, prefix-only checks unaffected). N×N 翻译矩阵对齐修复。New tests/translator-phase-fixes.test.js (13 cases).
  • 后端错误透出:/v1/responses 在后端 session 失败时曾 throw 纯对象,经 transformUpstreamError 被洗成 500 Internal server error / code Object、真实讯息丢失。新增 normalizeBackendError(纯对象→Error,保留 data.message/name/状态码推断),两处 throw polled.error 改用它;transformUpstreamError 纵深加固(默认分支改读 data.message/name,code 默认 upstream_error 不再用 constructor.name)。现 Credits 类错误正确回 402 insufficient_quota 并携带原文。新增 tests/backend-error-surface.test.js(4 例复现 LiteLLM 案例,含 messages)。
  • 同族错误路径查漏:responses 串流 idleTimeout 补 poll 失败检查(不再静默回空 completed);messages 非串流改用 transformed status/type/code(不再硬编码 502/api_error);messages 串流检查 collected.error 与 poll 失败(失败即抛正規化错误走 SSE error 事件,不再静默 200);isTransientUpstreamError 纳入 name/code/type 参与签名匹配(无 message 纯对象不再必判 false)。
  • Docker six-way symmetry (Stage 1): Dockerfile adds the missing empty ENV OPENCODE_PROXY_BIND_HOST= plus the five legacy ENV EXTERNAL_TOOL_*= aliases so image defaults stay empty and never shadow a mounted config.json or legacy alias; behavior is value-identical (empty == unset).
  • Stream tool-error surface unification (Stage 2 + final): /v1/chat/completions, /v1/messages, and /v1/responses stream:true fail-closed tool errors (parallel_external_tool_calls, external_tool_choice_*, invalid_external_tool_call, external_tool_policy_blocked, duplicate_external_tool_call_id, malformed_external_tool_call) now return the same stable code as non-stream (chat: 502 JSON before headers / SSE data: {error} after; messages: 502 JSON / SSE error event, api_error; responses: 502 JSON / response.failed + [DONE], server_error) instead of falling through to 500 internal_error via transformUpstreamError.
  • Collector ID-less tool tracking (Stage 2, review fix): counter becomes a per-tool-name Set (noid:<tool> / noid:unknown) so two concurrent ID-less backend tool parts no longer clear each other, while repeated message.part.updated snapshots of the same logical tool no longer inflate and pin the idle exemption until REQUEST_TIMEOUT_MS. Replayed terminal snapshots are ignored via a settled set (they must not delete a still-running bucket); name-mismatch fuzzy clearing applies to the single-bucket case only, multi-bucket ambiguity waits for the real terminal (waiting beats truncating). Locked by tests/stream-hardening.test.js (repeat, distinct-parallel, replay, mismatch both directions, same-name limitation).
  • Review confirmations (no behavior change): stream:true with any external tool contract intentionally buffers visible text until completion to avoid leaking <function_calls> markup incrementally (pure-text turns also batch — documented in route comments); non-stream if (error != null) intentionally fails even with partial content rather than returning partial as success on chat/messages/responses (retry still covers the no-output transient case; interactions is the deliberate exception — it only throws when there is no content at all, otherwise returns partial as interaction.completed); verified-empty tool discovery ([]) intentionally stays 503 fail-closed under DISABLE_TOOLS=true (a real backend always exposes tools; empty means unverifiable — locked by tests/tool-alias.test.js).
  • Explicit external-tool policy semantics (Stage 2, breaking note): REQUIRE_CONFIRMATION now fires only for proxy-authored requiresConfirmation===true or EXTERNAL_TOOL_REQUIRE_CONFIRMATION_FOR hits (plus report-only bypass); the old implicit WRITE/HIGH/CRITICAL/DELETE inference is removed, so a delete_repo tool with an empty allowlist now returns ALLOW instead of being blocked. Allowlist / denylist / confirmation lists are exact-match only (no wildcard/glob support; matching is checked against original / namespaced / declared names). Operators relying on default-deny for destructive tools should list exact tool names explicitly, e.g. EXTERNAL_TOOL_REQUIRE_CONFIRMATION_FOR=delete_repo,write_file. 白名单/黑名单/确认名单仅精确匹配,不支持 * 通配符(按原始名/命名空间名/声明名三者任一命中为准)。Parser stays fail-closed by design (valid plus malformed still rejects the batch with malformed_external_tool_call, locked by tests/app.test.js).
  • Wire-compat confirmations (Stage 3): upstream 503 now passes through as 503 server_error (was coerced to 502; tool_discovery_unavailable stays proxy-originated only, locked by tests/backend-error-surface.test.js); /v1/messages bare {type:tool} without name is 400 via validateAnthropicToolChoice (direct converter calls degrade to auto, locked by tests/messages-anthropic.test.js); empty tool_result is preserved as TOOL_RESULT content:"" to keep turn alignment (prompt breaking: previously dropped, now one extra prompt line); chat/messages/responses empty-stream stays 500 Upstream returned no assistant data (fail-closed, non-stream empty stays 200 — documented inconsistency, unify later); interactions storeResponseState intentionally omits tool calls (text+grounding only); native discovery 30s cooldown fail-closed is by design (no second tool.ids() probe inside the window).

[1.6.0] - 2026-09-08

⚠️ Breaking / 行為變更

  • DISABLE_TOOLS=false 從被忽略變為真正生效:舊版只讀 OPENCODE_DISABLE_TOOLS,經 compose/.env 設 DISABLE_TOOLS=false 的用戶實際跑的是預設 true(工具禁用)。本版起該別名正式生效,升級後工具會真正啟用並可執行後端操作。要保持禁用請顯式設 DISABLE_TOOLS=true(或改用 OPENCODE_DISABLE_TOOLS=true)或刪行回預設。
  • 串流錯誤語義(僅 /v1/responses 改變傳輸形態):stream:true 的 /v1/responses 在 headers 已 flush(早於 preflight)之後出錯時,改經 SSE response.failed 事件 + data: [DONE] 回報(HTTP 狀態固定為 200)。/v1/chat/completions 串流維持原形態(data: {"error":…} 後結束),/v1/messages 串流維持 error 事件後結束(無 [DONE]);且 chat/messages 的 preflight 失敗仍在 headers 送出前發生,故仍回 JSON 狀態碼(行為不變)。只看 HTTP 狀態碼的 responses 串流客戶端請改為同時監聽流內 response.failed;stream:false 的 JSON 狀態碼語義三路由皆不變(含超時 → 504)。

Added

  • Docker 多架构发布:ghcr.io/samson910022/opencode2api:latest 同时推送 linux/amd64 + linux/arm64(此前已上线但未记入 changelog;ARM 机器可直接 pull,无需本地 build)。
  • 环境变量兼容别名:docker-compose/.env 沿用的 DISABLE_TOOLS 正式生效(此前只有 OPENCODE_DISABLE_TOOLS 被读取)。解析收敛到共用的 normalizeBool + resolveDisableTools(无效值让位:canonical env > legacy env > config.json > 默认;數字僅 0/1 有效,其餘視為未設),index.js 直接調用同一 helper 不再手寫 ?? 鏈,附 tests/env-alias.test.js。
  • 串流强健(responses/chat/messages 三路由):串流 /v1/responses 在 preflight 等待前即 flush SSE 头 + 15s heartbeat(中转不再看到零字节 stall;chat/messages 的 headers 仍在 preflight 之後送出,其 preflight 失敗維持 JSON 狀態碼);三路由的 resolve/session/tool-overrides preflight 與非串流 prompt 全部经 withTimeout 限界(真错误立即透出,超时经 Request timeout 映射為 504);客户端断开经 res 'close' + !writableEnded 取消并清理 session(req 'close' 在 body 解析完即觸發,不可用);promptWithTimeout 統一改調 withTimeout(舊 inline race 洩漏 timer 且晚拒絕可致 unhandled rejection)。附 tests/stream-hardening.test.js。
  • 小机部署加固(可調):compose 加 mem_limit 768m/mem_reservation 256m/cpus 1.5/pids_limit 256、日志轮转、start_period 200s,补 OPENCODE_PROXY_RETRY_MAX_RETRIES/BIND_HOST/OPENCODE_DISABLE_TOOLS 透传;Dockerfile 补 RETRY 默认;entrypoint 后端等待 30s→约 180s(覆盖小机冷启动)。mem_limit 是部署行為變更(非 API breaking):此前無上限只會變慢,現超限會被 OOM kill;大機請用 docker-compose.override.yml 放寬(見 docs/docker.md「覆寫資源限制」)。

Fixed

  • DISABLE_TOOLS=false 被静默忽略:见上 Breaking(生产默认仍为 true,只在显式设 false/别名时行为变化)。
  • 断线侦测误用 req 'close':server 端该事件在请求体解析完即触发(实测 +1ms),不能用于串流断线;改用 res 'close' + !writableEnded(/v1/messages 旧 pattern 因监听挂得晚而等于失效,一并修复;監聽器改 once 避免殘留)。
  • withTimeout 超時誤回 500:超時訊息現為 Request timeout after …(label),可被 transformUpstreamError 正確映射為 504(此前自訂訊息繞過映射);createApp 補 REQUEST_TIMEOUT_MS 預設,非有限值回退到 DEFAULT_REQUEST_TIMEOUT_MS。
  • BIND_HOST 命名分裂:startProxy 後備鏈與 index.js 合併皆支援 BIND_HOST 優先、OPENCODE_PROXY_BIND_HOST 後備(此前 startProxy 只有後者,經正常 index.js 路徑時後備不可達)。
  • 文件範例收斂(非 breaking):docs/docker.md 的 docker run 範例刪除 -p 10001:10001(後端僅容器內部使用;運行時無變更,舊映射仍可用,建議改走代理端口)。

[1.5.0] - 2026-04-18

Added

  • External Tool Bridge: Added proxy-level bridging for external OpenAI-compatible tools across /v1/chat/completions and /v1/responses.
  • Streaming Tool Call Parity: Added streaming support for external tool calls in both Chat Completions and Responses APIs.
  • Explicit External Tool Config: Added explicit EXTERNAL_TOOLS_MODE=proxy-bridge and EXTERNAL_TOOLS_CONFLICT_POLICY=namespace configuration surface and documentation.

Changed

  • Project Version: Bumped the repository version to 1.5.0 across package metadata and documentation badges.

Fixed

  • Jest Test Shutdown: Removed a lingering queue rescheduling timer from the proxy request lock flow and updated the default test command to use the verified clean Jest invocation, eliminating the previous generic open-handle warning during npm test.

[1.0.0] - 2025-04-11

Added

  • OpenAI-compatible API: /v1/models, /v1/chat/completions, /v1/responses endpoints
  • Streaming Support: Full SSE streaming for Chat Completions and Responses API
  • Model Aliases: GPT-style model aliasing (e.g., gpt5-nano → gpt-5-nano)
  • Docker Deployment: Complete Docker setup with healthcheck and volume management
  • Configuration: Environment variables and config.json support
  • Auto Cleanup: Configurable automatic conversation/session storage cleanup

Changed

  • Default Security: DISABLE_TOOLS defaults to true for safer out-of-box behavior