Skip to content

Releases: dwgx/WindsurfAPI

v2.0.11

26 Apr 12:45

Choose a tag to compare

v2.0.11 — 实验性 sketch dashboard 皮肤 (cookie 切换)

新增:仪表盘可切换皮肤。默认仍是现代风(不动),加了一个手绘草稿风的实验性 UI 作为 opt-in 替代。

切换方式 (How to switch)

  • 设置入口:仪表盘"实验性功能"面板新增"界面风格 / Console skin"下拉选择。
  • 后端:cookie dashboard_skin=sketch 命中即返回 sketch HTML,否则返回默认 HTML。Vary: Cookie 防止中间层串味。
  • Sketch 皮肤里也有一个回切按钮(侧栏脚部)和一个面板里的下拉。

The dashboard now has a switchable skin. Default modern UI is unchanged; a hand-drawn sketch UI is added as an opt-in alternative.

  • Toggle: experimental panel → "界面风格 / Console skin" dropdown.
  • Backend: cookie dashboard_skin=sketch selects the sketch HTML; otherwise default. Vary: Cookie prevents intermediary cache poisoning.
  • Sketch skin also exposes a back-to-default button in its sidebar footer and a dropdown inside its experimental panel.

Sketch 皮肤逻辑修复 (audit 出来的 critical drift)

第三方贡献的 sketch UI 跟生产后端有几处 API contract drift,独立审计 (codex) 拉出来后这次都补齐了:

  • logs SSE:从 EventSource ?token= 改成 fetch + X-Dashboard-Password 头,payload 字段名 {ts, msg} 对齐生产;之前的写法对正式后端是 401 / 字段全空。
  • experimental routesPATCHPUTPOST /clear-poolDELETE /conversation-pool;读 flags.cascadeConversationReuse 而不是顶层。之前所有切换静默失败。
  • system-prompts editor:整段从默认 UI 移植过来 (loadSystemPrompts / saveSystemPrompt / resetSystemPrompt),sketch 现在能跟默认 UI 一样改重置 prompt 模板。
  • /stats 字段名 driftr.hourly / r.models / r.accountsr.hourlyBuckets / r.modelCounts / r.accountCounts(后端 shape 早就是这样)。之前 sketch 的 stats 永远是空。
  • /bans 端点不存在:sketch 改成跟默认 UI 一样从 /accounts 派生 ban 列表。原来的 /bans 调用是 404。
  • /credits:后端无此端点,sketch 现在 graceful fallback 到 GitHub PR 链接。
  • 原本 sketch 缺的 nice-to-have 全补齐:proxy 测试按钮 + 状态、batch-import 多行 proxy 语法说明、stats 范围切换 (24h/7d/30d)、清空统计按钮、stats + bans 的 30s 自动刷新。

Sketch skin contract drift fixes (called out by independent codex audit):

  • logs SSE: switched from EventSource ?token= to fetch + X-Dashboard-Password header; payload field names corrected to {ts, msg}. The previous wiring 401'd against the real backend and rendered no log content.
  • experimental routes: PATCHPUT, POST /clear-poolDELETE /conversation-pool; reads flags.cascadeConversationReuse instead of a top-level field. Earlier toggles silently reverted on next load.
  • system-prompts editor: ported over from the default UI (loadSystemPrompts / saveSystemPrompt / resetSystemPrompt); the sketch panel can now read/write/reset prompt templates the same way.
  • /stats response-shape drift: sketch was reading r.hourly / r.models / r.accounts; backend ships r.hourlyBuckets / r.modelCounts / r.accountCounts. Stats panel was empty until this release.
  • /bans nonexistent: sketch now derives the ban list from /accounts (same pattern as the default UI). The previous /bans call 404'd.
  • /credits: backend has no such route; sketch now gracefully falls back to a link to the GitHub PR list.
  • Nice-to-have catch-up: proxy test button + status line, batch-import help text covering the optional per-line proxy syntax, stats range presets (24h/7d/30d), reset-stats action, 30s auto-refresh on stats + bans.

Emoji → inline SVG

Sketch 里所有可见的 unicode 图标 (✓ ✗ → ↻ ▸ ☾ ☀ ✎ ↳ 📋 🔍 ♡ ⟳ ✱,共 30 处) 全部替换成 Lucide 风格的 inline SVG,零 npm 依赖,跨平台渲染一致。CSS content: 伪元素 (4 处) 留作字体装饰,不会渲染成 emoji。

All 14 unicode pictographs in the sketch UI (30 total occurrences) replaced with inline Lucide-style SVG. Zero npm deps. CSS pseudo-element content: rules are intentionally left as font glyphs.

兼容性 (Compatibility)

  • 默认 UI 行为完全没变。已有用户切换前不会感到任何区别。
  • 只新增了 cookie dashboard_skin,路径 /,max-age 一年,samesite=lax。
  • 同一台浏览器在不同 dashboard 端点之间共享 cookie;多 origin 部署需要分别切换。
  • Default UI behavior is unchanged. Existing users see no difference until they opt in.
  • New cookie dashboard_skin only; path=/, max-age=1y, samesite=lax.
  • Cookie is per-origin; multi-origin deployments switch independently.

致谢 (Acknowledgements)

  • Sketch UI 设计:Claude design 提供初稿,本版按 codex 5.4 审计 + claude 收尾把 API drift 全部补齐。
  • Sketch UI: original draft from Claude design; this release reconciles it against the production backend per a codex 5.4 audit + claude review pass.

v2.0.10

26 Apr 10:24

Choose a tag to compare

v2.0.10 — hotfix: dynamic cloud probe was crashing silently

v2.0.9 部到 staging 跑端到端 probe 时发现的:每次账号 refresh 周期日志里都报

[WARN] Dynamic cloud probe failed: positiveIntEnv is not defined

src/auth.js::probeAccount() 调用了 positiveIntEnv('MAX_CLOUD_PROBES', 10),但这个 helper 只在 src/client.jssrc/conversation-pool.js 里各自局部定义了,没在 auth.js 里声明也没 import。runtime 第一次跑 cloud-probe 路径就 ReferenceError,被外层 try/catch 吞了变成一行警告,free 账号的 cloud 候选模型发现完全不工作。不影响主代理路径,所以一直没人察觉。

修复 (Bug fix)

  • src/auth.js: 把 positiveIntEnv 在 module 顶部定义一份(跟 client.js / conversation-pool.js 同形态,零依赖)。

Bug fix

  • src/auth.js: define positiveIntEnv inline at the top of the module. The helper exists in the two other files that need it; auth.js just never got its copy and the cloud-probe path went silent.

致谢 (Acknowledgements)

  • 端到端 probe 在 Tokyo VPS 上发现:scripts/_v209_probe.py + pm2 logs grep。
  • Found while running scripts/_v209_probe.py against a v2.0.9 deployment on the Tokyo staging box and grepping pm2 logs for warnings.

v2.0.9

26 Apr 10:10

Choose a tag to compare

v2.0.9 — Cloud-deployment hardening (#67 / #68 / cloud tool-calling regression)

This release fixes three independent issues that compounded in cloud deployments:

  1. accounts.json was getting orphaned on docker-compose upgrades.
  2. Bare claude-4.6 requests silently fell through to a default model.
  3. Large tool catalogs (Claude Code, Cline, opencode, Codex CLI) blew past the upstream LS panel-state ceiling and tools were never called.

This release fixes all three. No upstream protocol changes.

修复 (Bug fixes)

  • #67 / docker-compose 升级账号丢失accounts.json 现在固定写在 cluster-shared 的 <DATA_DIR>/accounts.json,不再随 REPLICA_ISOLATE / 容器 HOSTNAME 变化。新增启动迁移:如果 shared 路径上没有 accounts.json 但发现一个或多个 replica-*/accounts.json(旧版本残留),自动按 apiKey 取并集写入 shared 路径。重复出现的 apiKey 取首次出现的记录。
  • docker-compose.yml 默认值改为单副本REPLICA_ISOLATE=0 + replicas: 1runtime-config.json / model-access.json / 响应缓存 / Cascade reuse pool 都还是按进程隔离,多副本是 opt-in,需要使用方自己提供外部协调。
  • #68 / claude-4.6 自报 4.5:根因是 catalog miss 的 silent fallback,不是身份重写缺失。resolveModel('claude-4.6') miss 后返回原字符串、getModelInfo 返回 null、chat.js 把请求路由进 legacy rawGetChatMessage 而 model name 都没传给上游,模型回到训练数据里"Claude 4.5"的自我认知。补 alias claude-4.6 / -thinking / -1m / -thinking-1m → 对应 claude-sonnet-4.6*,并让 chat.js 对未知模型直接返 400 model_not_found,不再 silent 降级。
  • Cloud 部署 tool calling 不工作buildCascadeConfig() 之前把同一份完整 tool schema blob 同时塞进 field 12(additional_instructions_section)和 field 10(tool_calling_section)。文件内已有的注释早已确认 NO_TOOL planner mode 抑制 field 10 — 它只是在膨胀 payload,给 30+ tools 的 Claude Code / Codex 请求把 panel state 推过 ~30KB 上限,触发 Panel state missing on Send 重试链直到放弃。field 10 现已不再注入
  • Tool preamble 容量护栏:proto-level preamble 超过 TOOL_PREAMBLE_SOFT_BYTES(默认 24KB)时自动降级为 names-only compact 形态(保留协议 + 工具名 + 环境块,丢弃参数 schema),超过 TOOL_PREAMBLE_HARD_BYTES(默认 48KB)直接返 400 tool_preamble_too_large,调用方明确知道要 trim。

Bug fixes:

  • #67 / docker-compose upgrades dropped accounts: accounts.json now lives at the cluster-shared <DATA_DIR>/accounts.json regardless of REPLICA_ISOLATE or container HOSTNAME. On startup, if the shared file is missing but one or more legacy replica-*/accounts.json files exist under the data dir (carry-over from prior versions or upgrade cycles with rotating hostnames), they are union-merged by apiKey (first-seen wins) and written to the shared path. Duplicate apiKey entries are dropped.
  • docker-compose defaults are now single-replica: REPLICA_ISOLATE=0 and replicas: 1. runtime-config.json, model-access.json, the response cache, and the Cascade reuse pool are still per-process; multi-replica is opt-in and requires external coordination for those state files.
  • #68 / claude-4.6 reported itself as 4.5: this turned out to be a routing fallthrough, not an identity-rewriting gap. claude-4.6 was missing from the alias table, resolveModel() returned the raw string on miss, getModelInfo() returned null, and chat.js routed the request through the legacy rawGetChatMessage path with no model name attached, so the upstream picked a default model whose self-knowledge predates 4.6. Added explicit aliases (claude-4.6 / -thinking / -1m / -thinking-1m → corresponding claude-sonnet-4.6*) and made unknown models return 400 model_not_found instead of silently degrading.
  • Cloud-deployment tool-calling regression: buildCascadeConfig() was injecting the same full tool schema blob into both additional_instructions_section (field 12) and tool_calling_section (field 10). The in-file comments already established that NO_TOOL planner mode suppresses field 10 — it was bloating payload without contributing. With 30+ tool catalogs (Claude Code, Cline, opencode, Codex CLI) the doubled blob pushed total LS panel state past the documented ~30KB ceiling and tools silently failed via the panel-state retry path. Field 10 is no longer written.
  • Tool preamble byte budget: when the proto-level preamble exceeds TOOL_PREAMBLE_SOFT_BYTES (default 24KB) it is replaced with a names-only compact form (protocol + tool names + environment block, no parameter schemas). When it exceeds TOOL_PREAMBLE_HARD_BYTES (default 48KB) the request returns 400 tool_preamble_too_large so the caller knows to reduce tool count or shrink schemas.

兼容性 (Compatibility)

  • 未知模型不再 silent fallback — 之前依赖拼写错误回退的调用方现在会拿到 400。这是行为变更,但是正确方向。
  • docker-compose 多副本变成 opt-in。如果你之前依赖默认的 3 副本部署,请显式 docker compose up -d --scale windsurf-api=3 并接受 runtime-config.json / model-access.json / 缓存 / cascade pool 仍按进程隔离;后续版本会把这些外部化。
  • claude-4.6(无 sonnet/opus 后缀)现在路由到 sonnet 变体;如果你之前明确想要 opus 请改用 claude-opus-4.6

Compatibility:

  • Unknown model names no longer silently degrade — callers that relied on typo fallback now get an explicit 400. Behavior change, but the correct direction.
  • Multi-replica docker-compose is now opt-in. If you previously relied on the 3-replica default, scale explicitly with docker compose up -d --scale windsurf-api=3 and accept that runtime-config.json / model-access.json / cache / cascade pool stay per-process for now; externalizing them is on the roadmap.
  • Bare claude-4.6 (no sonnet/opus suffix) routes to the sonnet variant. Use claude-opus-4.6 explicitly if you wanted opus.

新环境变量 (New environment variables)

  • TOOL_PREAMBLE_SOFT_BYTES (default 24000) — switch to names-only proto preamble above this size.
  • TOOL_PREAMBLE_HARD_BYTES (default 48000) — return 400 tool_preamble_too_large above this size.

测试覆盖 (Test coverage)

  • 全套 174 个测试通过(v2.0.8 = 156)。新增:
    • test/auth-migration.test.js — 7 cases 覆盖 replica-* 子目录单/多源迁移、apiKey 去重、损坏 JSON 容错、空目录跳过、shared 已存在短路、sharedDir 不存在。
    • test/tool-emulation.test.js — 7 cases 覆盖 compact preamble 与 full schemas 的尺寸比、所有工具名保留、参数 schema 完全删除、环境块保留、tool_choice=required 正确、空 tools 边界、无 jailbreak 措辞。
    • test/models.test.js — 2 cases 覆盖 bare claude-4.6 系列 alias resolve + getModelInfo 落到真实 catalog 条目。
    • test/tool-preamble-forbidden-words.test.js — 扩展覆盖 compact preamble。

Test coverage:

  • All 174 tests pass (v2.0.8 had 156). New:
    • test/auth-migration.test.js — 7 cases covering single/multi-source migration from replica-* subdirs, apiKey dedup (first-seen wins), corrupt-JSON tolerance, empty subdir skip, short-circuit when shared accounts.json already exists, missing sharedDir.
    • test/tool-emulation.test.js — 7 cases for the compact preamble: size ratio vs full schemas, every tool name preserved, schemas fully omitted, environment block preserved, tool_choice=required handled, empty-tools edges, no jailbreak phrasing.
    • test/models.test.js — 2 cases for bare claude-4.6* resolve + getModelInfo lands on a real catalog entry.
    • test/tool-preamble-forbidden-words.test.js — extended to cover compact preamble paths too.

致谢

  • 报告 #67 / #68 / #69@lihengcn
  • 项目级审计:codex 5.4 high-reasoning(archive 在 tmp/audit-report-2026-04-26-claude-takeover.md

Acknowledgements:

  • Reports for #67 / #68 / #69 by @lihengcn.
  • Project-wide audit driven by codex (gpt-5.4 high-reasoning); archived as tmp/audit-report-2026-04-26-claude-takeover.md.

v2.0.8

26 Apr 08:39

Choose a tag to compare

v2.0.8 — 紧急登录修复 (Emergency login fix)

Windsurf 在 2026-04-26 当天调整了登录链路(半迁移状态),导致 dashboard 添加账号偶发 / 持续失败。本版只针对登录链路加固,无其他改动。

Windsurf reshuffled their login flow on 2026-04-26 (mid-migration), causing the dashboard "add account" flow to fail intermittently or persistently. This release is a focused login-path hardening; no other changes.

修复 (Bug fixes)

  • CheckUserLoginMethod 为主邮箱探测(Connect-RPC,响应形态 {userExists, hasPassword},跑在 Windsurf _backend 后端,响应快、稳定)。
  • /_devin-auth/connections 路径降级为 fallback,并新增对其新 schema ({connections:[{type,enabled,...}]}) 的兼容;老 schema ({auth_method:{method,has_password}}) 仍兼容。
  • _devin-auth/* 系列端点跑在 Vercel functions 上时不时 504 / 503 (FUNCTION_INVOCATION_TIMEOUT):增加 5xx 退避重试 (3 次, 0/2s/5s),4xx 仍直通不重试。
  • password/login 的错误 detail 字段从 string 变成 Pydantic v2 的 array 时不再触发 .toLowerCase 数组炸栈,统一拼成可读字符串。

Bug fixes:

  • Promoted CheckUserLoginMethod (Connect-RPC) to the primary email probe. The new endpoint returns a clean {userExists, hasPassword} and runs on Windsurf's _backend cluster; responses are fast and stable.
  • Demoted /_devin-auth/connections to fallback and added compatibility for its new schema ({connections:[{type,enabled,...}]}); the old {auth_method:{method,has_password}} shape is still accepted.
  • Added 5xx exponential-backoff retries (3 attempts at 0 / 2s / 5s) for _devin-auth/* endpoints, which intermittently timeout on Vercel functions; 4xx responses still skip retries.
  • password/login now safely normalizes the detail field whether it comes back as a string (legacy) or an array of Pydantic v2 validation errors.

致谢

  • Repro / 反馈:@Wenlong-Guo (#66)
  • 路径迁移定位:codex worker (Playwright 抓 Windsurf 网页登录流量)

Acknowledgements:

  • Repro / report: @Wenlong-Guo (#66)
  • Migration localization: codex worker (Playwright capture of Windsurf web login traffic)

v2.0.7

26 Apr 06:48

Choose a tag to compare

v2.0.7 — Audit-driven hardening + Codex/Anthropic spec compliance

修复 (Bug fixes)

  • 修复 #59 sub-bug 3 工具边界文本被切两段:ToolCallStreamParser.feed() 之前返回 {text, toolCalls} 两个独立数组,丢失了 text 与 tool_call 在同一 chunk 内的相对顺序,导致 C. 我不会 / Read 工具 / 修改文件,也不会扩大 scope 这种"句子被工具切两段"的现象。新增 items 字段保留顺序,stream 消费方按 items 顺序 emit;text/toolCalls 保留向后兼容。
  • 修复 #66 限流时间精度与 cooldown 累积偏差:rateLimitCooldownMs 现在解析 retry after N seconds/minutes/hours 文案;markRateLimited 改为 Math.max(existing, new),并发 429 不再把 cooldown 不断后推;preflight 没拿到具体 retry 时间时不再本地标 cooldown,本次 skip 即可。
  • 修复 #59 sub-bug 2 长输出被 180s 硬截断:CASCADE_MAX_WAIT_MS 默认从 180s 提到 600s,warmStallMs=25s 仍负责真正卡死的 cascade 退出,给慢但持续流的长输出留足空间。
  • 修复 #63 streaming response.completed 被静默丢弃:/v1/responses 生命周期事件(created / in_progress / completed / failed)payload 改为 OpenAI 规范的 { response: {...} } envelope;新增 sequence_number 单调递增字段;补 response.in_progress 事件。
  • 修复 cache HIT 流式分支与 live-stream chunk shape 不一致:cache HIT 也拆成独立的 finish_reason chunk + 单独的 usage chunk,跟 live-stream 路径同形。
  • 修复全账号 RPM 满返 503:现在返 429 + Retry-After 头让客户端能正常重试,503 只在真没账号时返。
  • 修复 preflight skip 占住本地 RPM headroom:account 增加 _lastReservationAt,preflight !hasCapacity 路径自动 refundReservation 退回 _rpmHistory

Bug fixes:

  • Fixed #59 sub-bug 3 tool-boundary text split: ToolCallStreamParser.feed() previously returned {text, toolCalls} as separate arrays and lost the relative order of text and tool_call within a single chunk, producing "sentence cut by a tool call" symptoms like C. 我不会 / Read / 修改文件…. Added an ordered items field; stream consumer now emits in order; text/toolCalls kept for back-compat.
  • Fixed #66 rate-limit cooldown precision and accumulation drift: rateLimitCooldownMs now parses explicit retry after N seconds/minutes/hours; markRateLimited uses Math.max(existing, new) so concurrent 429s don't keep extending cooldown; preflight without explicit retry time only skips this attempt instead of poisoning local cooldown.
  • Fixed #59 sub-bug 2 long-output 180s truncation: raised CASCADE_MAX_WAIT_MS default from 180s to 600s; warmStallMs=25s still exits genuinely-stalled cascades.
  • Fixed #63 silently-dropped response.completed: /v1/responses lifecycle events (created / in_progress / completed / failed) now use the OpenAI-spec { response: {...} } envelope; added monotonic sequence_number; added the missing response.in_progress.
  • Fixed cache-hit stream chunk shape mismatch with live stream: cache-hit path also splits into a finish_reason chunk + a separate usage chunk, matching live-stream shape.
  • Fixed all-RPM exhaustion returning 503: now returns 429 + Retry-After so clients can retry; 503 reserved for the genuinely no-account case.
  • Fixed preflight skip still consuming local RPM headroom: account adds _lastReservationAt; the preflight !hasCapacity path auto-refunds the reservation in _rpmHistory.

兼容性与规范 (Compatibility / spec)

  • /v1/responses 拒绝非 function tools(如 web_search_preview)改为 400 错误,避免静默 drop 后客户端语义已变;function-call-only 响应不再带空 message item 在 output
  • /v1/messages 透传 Anthropic thinking 字段;tool_choice 映射到 OpenAI 形状(autoautoanyrequiredtool/name{type:'function',function:{name}}nonenone)。
  • /v1/responses 流式新增 Codex CLI v0.125 期望的最小 envelope:{"type":"response.completed","response":{"id":"resp_…",…},"sequence_number":N};非生命周期事件(output_item.* / content_part.* / output_text.* / reasoning_. / function_call_arguments.*)shape 不变。

Compatibility / spec:

  • /v1/responses rejects non-function tools (web_search_preview, etc.) with 400 instead of silently dropping; function-call-only responses no longer include an empty message item in output.
  • /v1/messages now passes through Anthropic thinking; tool_choice is mapped to OpenAI shape (autoauto, anyrequired, tool/name{type:'function',function:{name}}, nonenone).
  • /v1/responses streaming now emits the minimal envelope Codex CLI v0.125 expects: {"type":"response.completed","response":{"id":"resp_…",…},"sequence_number":N}. Non-lifecycle events (output_item., content_part., output_text., reasoning_., function_call_arguments.) shape unchanged.

CI / 发布流程 (CI / release)

  • 新增 .github/workflows/release.yml:推 v* tag 时自动构建 linux/amd64 Docker 镜像推 GHCR + 创建 GitHub Release(RELEASE_NOTES_x.y.z.md 自动作为 body,含 - 自动 prerelease)。
  • docker-compose.yml 同时保留 image:build::默认拉预构建镜像,docker compose up --build 仍可本地源码迭代。
  • 修正镜像命名 windsurfapiwindsurf-api(与 package.json 项目名对齐)。

CI / release:

  • Added .github/workflows/release.yml: pushing a v* tag now auto-builds the linux/amd64 Docker image to GHCR and creates a GitHub Release (with RELEASE_NOTES_x.y.z.md as the body; tags containing - become pre-releases).
  • docker-compose.yml keeps both image: and build:: pulls the prebuilt image by default, docker compose up --build still iterates locally.
  • Image name corrected to windsurf-api (aligned with package.json project name).

测试覆盖 (Test coverage)

  • 全套 158 个测试通过(v2.0.6 = 144),新增 12 个:parser ordered-items 、cooldown 解析、preflight refund、429 not 503、/v1/messages thinking + tool_choice 透传、/v1/responses 非 function tools 与空 message item、cache-hit chunk shape 与 live-stream 同形等。
  • Mac arm64 (node v24.13.0) 同样 158/158 通过,跨平台无回归。

Test coverage:

  • All 158 tests pass (was 144 in v2.0.6); 12 new: parser ordered items, cooldown parsing, preflight refund, 429 not 503, /v1/messages thinking + tool_choice passthrough, /v1/responses non-function tools and empty message items, cache-hit chunk shape parity with live stream.
  • Mac arm64 (node v24.13.0) also passes 158/158, cross-platform clean.

致谢

  • 本版基于 gpt-5.5 high-reasoning 全项目代码审计(296 行 P0/P1/P2 报告,归档在 tmp/audit-report-2026-04-26.md,未发版)。
  • CI / release workflow by @abwuge (PR #65)。
  • Codex CLI v0.125 SSE 解析路径反查与 envelope 规范定位由 codex worker 完成。
  • 关联 issue: #59, #63, #66

Acknowledgements:

  • Driven by a gpt-5.5 high-reasoning project-wide audit (296-line P0/P1/P2 report, archived at tmp/audit-report-2026-04-26.md).
  • CI / release workflow by @abwuge (PR #65).
  • Codex CLI v0.125 SSE parser RE and envelope spec localization by the codex worker.
  • Related issues: #59, #63, #66.

v2.0.6 — Security hardening: caller 隔离 / SSRF / 日志脱敏 / #57 thinking-stall 修复

25 Apr 19:18

Choose a tag to compare

v2.0.6 — Hardening release

安全 (Security)

  • 修复 Cascade conversation pool 的跨调用方复用风险:fingerprint 现在包含 callerKey,API key、dashboard session 或 IP/User-Agent 会隔离相同首轮 prompt 的 hidden state。
  • 修复多模态响应缓存污染:图片和 PDF data URL 的 MIME、内容 hash 与字节数进入 cache key,避免同文本不同图片命中旧回答。
  • 代理、请求日志和 dashboard 登录返回值加强脱敏:LS proxy 日志不再输出账号密码;请求 probe 默认只记录长度和 hash;登录取号默认只返回 masked API key,并提供受 dashboard auth 保护的 reveal-key 接口。
  • 加强 SSRF 防护:统一 DNS 后校验,覆盖 IPv4 私网、link-local、loopback、CGNAT、IPv6 ULA/link-local/loopback 和 IPv4-mapped IPv6。
  • PDF FlateDecode 增加 zip bomb 防护:单 stream、累计解压和 stream 数量都有限制,超限时降级为“PDF 内容无法提取”。
  • 未配置 API_KEY / DASHBOARD_PASSWORD 且服务监听非 localhost 时,启动横幅会输出中英双语高强度警告,保持兼容但明确暴露风险。

Security:

  • Fixed cross-caller Cascade conversation reuse by including callerKey in fingerprints, isolating identical prompts by API key, dashboard session, or IP/User-Agent fallback.
  • Fixed multimodal cache poisoning by adding image/PDF MIME, content hash, and byte size to cache keys.
  • Hardened proxy, request, and dashboard key redaction: LS proxy logs no longer expose credentials, request probes default to length/hash, and login endpoints return masked API keys by default with a dashboard-authenticated reveal endpoint.
  • Strengthened SSRF checks with post-DNS validation for private IPv4, link-local, loopback, CGNAT, IPv6 local ranges, and IPv4-mapped IPv6.
  • Added PDF FlateDecode zip-bomb limits with a graceful “PDF 内容无法提取” fallback.
  • Added loud bilingual startup warnings when auth is not configured on non-localhost binds.

修复 (Bug fixes)

  • 修复 #57 thinking-only 长流被 cold-stall 误杀的问题,并将 stall 分类拆成 transient_stall / model_error,避免污染 capability 与限流判断。
  • 修复 Responses API 对 reasoning_content 的流式和非流式映射,输出 Responses reasoning item 与 summary delta。
  • 修复 dashboard batch import 使用 result.accountId 导致 per-account proxy 未绑定的问题,现在读取 result.account.id
  • 修复三层 stream 错误协议:Chat 发送结构化 OpenAI error SSE,Messages 转成 Anthropic event: error,Responses 转成 response.failed
  • 修复 Linux auto-install 缺少 dirname/fileURLToPath/join import 的问题。
  • 修复 account reprobe 并发入口,避免重复 probe 同时运行。

Bug fixes:

  • Fixed #57 cold-stall false positives during thinking-only streams, and split stall/model classifications to avoid capability and rate-limit pollution.
  • Mapped reasoning_content for both streaming and non-streaming Responses API output.
  • Fixed dashboard batch import proxy binding by reading result.account.id.
  • Structured stream errors across Chat, Anthropic Messages, and Responses protocols.
  • Fixed missing imports in the Linux language-server auto-install path.
  • Added a reprobe in-flight guard to prevent overlapping account probes.

健壮性 (Robustness)

  • SIGTERM/SIGINT shutdown now aborts active SSE streams with a structured server shutting down error before draining HTTP connections.
  • Cache normalization now includes response_formatreasoning_effortthinkingstream_options,避免不同请求选项互相污染。
  • Proto tool preamble 去除 Ignore / --- 等 jailbreak-shaped wording,并新增 forbidden wording 回归测试。
  • 新增 23 个回归测试,覆盖 caller 隔离、cache 多模态 key、proxy 脱敏、stream error、SSRF、PDF 限制、auth warning、SSE registry 和 tool preamble 禁词。

Robustness:

  • SIGTERM/SIGINT now abort active SSE streams with a structured server shutting down error before HTTP drain.
  • Cache normalization now includes response_format, reasoning_effort, thinking, and stream_options.
  • Removed jailbreak-shaped wording such as Ignore and --- from the proto tool preamble and added forbidden-word regression tests.
  • Added 23 regression tests covering caller isolation, multimodal cache keys, proxy redaction, stream errors, SSRF, PDF limits, auth warnings, SSE registry, and tool preamble forbidden wording.

致谢

  • 本版改动由 codex 全项目审计驱动;致谢 dwgx 主审 + codex worker
  • 关联 issue: #57, #59

Acknowledgements:

  • This hardening release was driven by the codex full-project audit; thanks to dwgx for primary review and the codex worker.
  • Related issues: #57, #59

v2.0.5 — Codex CLI / Opus 4.6 multimodal / pool transient handling

25 Apr 18:32

Choose a tag to compare

TL;DR

围绕「上游瞬态 + Opus 多模态 + Codex CLI 兼容」三条线收尾。涵盖外部贡献者 4 个 PR + 反代内部 5 处 surgical fix + dashboard 重大 escape regression hotfix。

新功能

  • POST /v1/responses 端点#56 #63)—— OpenAI Responses API 兼容层。新版 Codex CLI 强制 wire_api = "responses",原本 404 直接挂;现在 input/items/instructions/tools 全 map,流式完整 9 事件序列含 response.completed,function_call / function_call_output round-trip 工作。

Bug 修复

  • upstream_transient_error 错误类别#28)—— Cascade 上游 9 个账号都返 internal error occurred 时不再误报"速率限制",账号间加 0.5s→5s 指数退避避免瞬态雪崩。
  • 空 user.content 拒绝#28 OpenClaw probe 副产物)—— 之前会让模型对系统提示自言自语,现在直接 400 invalid_request_error
  • Opus 4.6 reuse + multimodal fallback 扩展#59)—— 之前只 4.7 走的 strict-reuse / 禁用 user-message tool fallback 现在 4.6 也走,匹配 dotted(claude-opus-4.6)和 dashed(claude-opus-4-6-medium)两种命名。
  • Stream 路径错误优先级修正(cold-eyes audit)—— upstream_transient_error 优先于 rate-limit 消息。
  • 24KB 重 system prompt 警告日志#28)—— OpenClaw / opencode / Cline 这种逼近 panel-state 阈值的客户端排查用。

贡献者 PR(按时间)

  • PR #62 @baily-zhang —— dashboard inline-script escape critical fix + V8 静态解析 regression test
  • PR #61 @baily-zhang —— Opus 4.7 + Claude Code + 图片 + tools 上下文爆炸根治
  • PR #58 @abwuge —— docker-compose 部署死锁修复(nginx zone + config.js join import)
  • PR #54 @aict666 —— tool preamble 1600→330 字符 + redact marker 第 6 代 U+2026 省略号 + neutralizeCascadeIdentity 5 个 pattern

测试

121/121 全过(v2.0.4 是 97/97,新增 24 个回归断言)。

部署

git pull origin master
# Docker
docker compose up -d --build
# 或 pm2
pm2 restart windsurf-api --update-env

完整 commit log

gh release view v2.0.4..v2.0.5 或看 Compare

v2.0.3 — 上下文稳定性 + Dashboard 大升级

24 Apr 16:15

Choose a tag to compare

v2.0.3 — 上下文稳定性 + Dashboard 大升级

今天一整天围绕 issue #24 上下文丢失 + #48 system prompt 注入残留 的打磨 已全部实测验证

🔧 issue #24 — Agent 场景文件幻觉 + Read 循环

Cascade 偶尔在响应里幻觉 /tmp/windsurf-workspace/xxx 这种沙箱路径(训练分布遗留)导致客户端循环

之前三代尝试都反噬

尝试 问题
改写为 ./tail Claude Code Read 本地 ./src/main.py ENOENT 循环
改为 [internal] LLM 把 [internal] 当目录名 ls [internal] 继续循环
改为 <redacted-path> LLM 把它当 file_path 喂给 Read 工具 Windows 还 Errno 22 崩溃

v2.0.3 最终方案:改为自然语言 (internal path redacted) — 多词带空格带括号 既不是路径格式 也不是 token 客户端和 LLM 都不会把它当文件用 drift probe 实测 sonnet 不再崩溃

同时强化 NO_TOOL 的 additional_instructions proto 字段:从弱指令"你没有工具"改为行为级硬约束 列清具体禁止的 agent 叙述句式("Let me check X" / "Looking at the file...")+ 给 fallback 模板 实测"问模型 src/main.py 是什么"不再编代码 直接回 "I don't see that file in our conversation — please paste it"

🔧 issue #24 — Panel state not found 重试加固

opencode + omo 插件场景(30KB+ system prompt + 30+ tools)LS 会快速失效 panel state 单次 retry 不够

  • Send 失败后 最多 retry 3 次 每次 full warmup + 新 StartCascade
  • retry 间 250ms × n backoff 让 LS 落地新 panel state
  • 3 次失败报清晰错误指向大 payload 本身是上游 LS 的上限

🔧 Tool-emulation 协议加固

合并 PR #50 @aict666:tool-emulation 请求跳过 cascade_id 复用(tool_use/tool_result body 每次变 fingerprint 命中率本来就近 0 reuse 只是徒增 pool 污染)

新增 safeParseJson lenient 解析:claude-4.5-haiku 偶尔输出 <tool_call>{...}}}</tool_call> 尾部多一个 } 之前严格 JSON.parse 失败直接把 <tool_call> 字面量回给客户端 现在宽容扫描第一个平衡 JSON 对象

🔧 issue #48 — 移除自动 system prompt 注入

用户报"关了模型身份注入开关还是有很多 system prompt"

  • injectLanguageHint 删除 — CJK 消息无条件追加英文 [IMPORTANT: You MUST respond in Chinese] 即使开关关了这条还在跑
  • modelIdentityPrompt 删除 — 连带 identity prompts 模板编辑器 per-provider 模板 /dashboard/api/identity-prompts 端点 Dashboard UI 全部清理
  • 保留用户主动触发:response_format=json_* JSON hint / tools[] → tool-emulation preamble
  • 保留响应侧:neutralizeCascadeIdentity("I am Cascade" → "I am {model}")只改响应不碰请求

✨ Dashboard 大升级

账号管理:配额详情面板

每行账号前加 展开四个分区:

  • 订阅信息:套餐/层级/开始-到期/Trial 剩余天数/透支余额
  • 配额使用:日/周大 bar + 重置时间 + Prompt Credits + Flex Credits
  • 可用模型:按 provider 分组彩色 swatch(Anthropic/OpenAI/Google/DeepSeek/xAI/Alibaba/Moonshot/Zhipu)每个模型 chip 显示每次调用扣多少 credit
  • 运行状态:status/RPM/最后使用/最后探测/完整 ID + Key/上次错误

统计分析:4 种图表切换 + 日期范围

  • 图表类型:面积图 / 折线图 / 柱状图(默认) / 堆叠图
  • 日期范围:6小时 / 24小时 / 7天 / 30天 / 全部 / 自定义(原生 date picker)
  • 数据存储:hourly buckets 从 72 扩到 720(30 天)
  • 动画:480ms ease-out 柱从 0 长起 折线从 baseline lerp 数据点淡入
  • 模型分布饼图:top 8 模型请求量占比 + center total label
  • tooltip:pinned 在 canvas 内部(之前 translate(-100%) 会跑出 header)

🔧 hvoy.ai 验真签名

  • openai-processing-ms 从硬编码 0 改为实际耗时(hvoy.ai 模型签名验证探测点)
  • 新增 openai-organization: org-windsurf-proxy header
  • response.model 回传 request 原始名 不再是 Windsurf 内部 alias(之前 claude-opus-4-7 request 返回 claude-opus-4-7-medium 触发 hvoy.ai 模型一致性 fail)
  • identity prompt 注入的模型名剥离 -medium/-low/-high reasoning-effort 后缀 避免 Claude 把"claude-opus-4-7-medium 不是我认识的模型"判为 prompt injection

📊 测试 & 实测

  • 56 个单元测试 100% 通过
  • 新增实测 probe(已删除未入库):5 类症状 × 2 模型 = 34 断言
    • 上下文保持 6/6 ✓
    • 跑题/牛头不对马嘴 10/10 ✓
    • 大 payload panel state (23KB + 30 tools × 5 轮) 2/2 ✓
    • 幻觉(PDF/image/虚构文件)6/6 ✓
    • Coding agent(含真实 bug 发现)7/10

📦 部署

  • 154.40.36.22:8996 master HEAD 已推 pm2 管理
  • Base URL: http://154.40.36.22:8996/v1 (OpenAI) / /v1/messages (Anthropic)

🙏 贡献

Commits

  • 9120b8b fix(#24): redact marker → (internal path redacted)
  • 2e0a2b1 fix: lenient JSON parse for <tool_call> bodies
  • 64be622 fix(#24): retry panel-state-missing up to 3 times with backoff
  • 5cb49e0 Merge PR #50 — disable Cascade reuse for tool-emulated turns
  • ec8c7c7 fix(#24): redact leaked paths (中间态 <redacted-path>)
  • e48eb5e fix(#24): stop Cascade file-path hallucination + read-loop
  • 2d3e569 feat(#48): remove all automatic system-prompt injection
  • 955b837 feat(dashboard): rich per-account quota detail panel
  • 4e2b8ca feat(dashboard): polished charts + date range + drop prompt bar
  • 06f0940 feat(dashboard): default to bar + chart animation + pinned tooltip
  • 85ea924 feat(dashboard): 4-type chart switcher + model pie + quota column i18n fix
  • ad540f3 fix: model-signature headers + phantom-attachment guard
  • 1f46a72 fix: robust JSON extraction + full OpenAI PDF input coverage
  • e8a6f42 fix: response.model echoes request.model verbatim

v2.0.2 — 结构化输出 + PDF 识别 + #47 工具调用修复

24 Apr 05:26

Choose a tag to compare

v2.0.2

修复

  • #47 工具调用 bugif (emulateTools) 条件导致解析出的 tool calls 被静默丢弃,Claude Code 看到的是空文本而非 tool_calls 数组。三处条件全部移除。

新功能

  • 结构化输出response_format: { type: "json_object" }json_schema 模式。双层注入(system message + user hint + schema 内容)+ response markdown fence 自动剥离。
  • PDF 文档识别:零依赖 PDF 文本提取器 (src/pdf.js)。支持 Anthropic type: "document" content block。FlateDecode 解压 + Tj/TJ 操作符解析。扫描件返回明确提示。
  • 思维链自动升级:请求带 thinkingreasoning_effort 参数时自动选择 thinking 变体模型。

v2.0.1 — hvoy.ai 验真优化 + 结构化输出 + 思维链

24 Apr 04:52

Choose a tag to compare

v2.0.1

基于 v2.0.0,针对 hvoy.ai 验真评分优化:

修复

  • 结构化输出:支持 response_format: { type: "json_object" }
  • 思维链自动升级:请求带 thinking 参数时自动选 thinking 变体模型
  • 模型签名 headers:x-request-id / openai-model / openai-version / anthropic-model
  • Response 身份替换:"I am Cascade" → "I am {model}, made by {provider}"
  • Tier 降级修复 (PR #44 @aict666)
  • Cascade reuse offset 增量拉取 (PR #45 @baily-zhang)
  • Dashboard 完整 i18n (PR #43 @smeinecke)

完整 changelog 见 v2.0.0 release notes。