feat(models,devin-acp): add swe-2 + route Devin Local-only models through ACP - #266
feat(models,devin-acp): add swe-2 + route Devin Local-only models through ACP#266Jakevin wants to merge 1 commit into
Conversation
|
评审:方向有价值,但这一版不能合。 你自己也说了只跑了 3 个测试文件 —— 我跑了全量,它新增了 1 条失败;而且那条失败背后是一个真实的行为变更,不是等价重写。下面 6 条都能单独验,改完仍然欢迎。 head
M1(阻塞)—
|
…gpt-5.6 SWE-2 medium/high/max from docs.devin.ai (credit 6/9/12). Connect deployments read them from the snapshot; Cascade/LS deployments with the CLI enabled use backend=special_agent (same as swe-1.6). gpt-5.6 luna/sol/terra stay on Cascade unless DEVIN_CLI_ENABLED=1. ACP does not invent api_server_url, does not default DEVIN_PERMISSION_MODE, and names itself WindsurfAPI unless DEVIN_ACP_CLIENT_NAME is set. Model is passed as --model only.
4889dfc to
9bfaa8a
Compare
|
改完了,head 按你列的逐条: M1 — 恢复 M2 — M3 — 两个部署面共用 catalog id,不抢一条路径:
M4 — 只发 M5 — gpt-5.6 正则改成锚定 SKU: M6 — 去掉 方言: 本机(Node 22.23.2): exit 0。请再跑全量。 |
|
复审:M1–M6 我逐条驱动验过,没有一条是"diff 看起来像修好了",全部真修好了。当前形态我判"可合,带两条小项"。 head ★ 先说一件和你这次改动无关、但直接影响你上一轮证据的事
而且这不怪你: 我驱动了什么(全部在 worktree
|
| 项 | 命令 / 手段 | 结果 |
|---|---|---|
| 全量 ×2 | npm test(= node --import ./test/setup-env.mjs --test test/*.test.js,无 --test-force-exit) |
4291 / 4286 / 5 ×2 次逐字相同;失败的文件正好是那 5 个 Unix-git 机器闸 |
| master 基线 | 另开 worktree 到 2265f59 跑同一命令 |
4276 / 4271 / 5 —— 失败文件集合与你的 head 完全相同 ⇒ 零新增失败 |
| 你点名的 4 个文件 | --test 4 个文件(不带 force-exit) |
349 / 349 / 0,exit 0 |
| 稳定性抽样 | --test-reporter=tap test/tool-emulation.test.js ×5 |
# tests 72 / # pass 72 / # fail 0 ×5,逐次相同 |
| 三道轻门 | node --check src/index.js / node src/dashboard/check-i18n.js / npm run secret-scan |
全部 exit 0 |
| 突变 anchor | node scripts/spec-static-check.mjs |
exit 0,48 specs / 576 mutations ✓ anchors unique(与 master 同;本 PR 未碰任何 spec,所以没有 anchor 漂移面) |
| M1 | 替换 __setDevinAcpSpawnForTest spawn 缝,抓 authenticate 帧原文 |
见下 |
| M2/M3/M5 | 用 src/models.js 的真实解析 + backend-router.selectBackend + 真 handler handleChatCompletions |
见下 |
| M4 | 真 handler,CLI 开 + mode 默认(print)+ 带 tools[] |
见下 |
M1(阻塞项)— 已修,我拿到了帧本身
api_server_url 只有账号真有才发:
| 场景 | 子进程 env DEVIN_MODEL |
子进程 env DEVIN_PERMISSION_MODE |
initialize.clientInfo |
authenticate._meta |
|---|---|---|---|---|
池账号无 apiServerUrl |
不存在 | 不存在 | {name:'WindsurfAPI',version:'3.9.32'} |
{"api_key":"K"} |
池账号有 apiServerUrl |
不存在 | 不存在 | 同上 | {"api_key":"K","api_server_url":"https://server.self-serve.windsurf.com"} |
DEVIN_ACP_CLIENT_NAME=windsurf |
不存在 | 不存在 | {name:'windsurf'} |
{"api_key":"K"} |
...(apiServerUrl ? { api_server_url: apiServerUrl } : {}) 已经在 src/devin-acp.js:556,不再无条件声明公共端点。test/devin-acp-integration.test.js 12/12,含那条 omits api_server_url ...(SERVER=|)。我按你上一条 review 记的量复现了:master 上 12/12 绿、旧实现(|| 'https://server.codeium.com')会红。这条我判 fixed,而且是帧级证据,不是读代码。
M2(设计)— 已修,我用生产形状驱动了
devin_local_only 现在碰不到 swe-2*,走的是既有的 backend === 'special_agent' 短路:
swe-2 / swe-2-medium / swe-2-high / swe-2-max / swe-2.0 / swe-2-0 / swe2
backend=special_agent -> reason=modelInfo.backend=special_agent
真 handler 上也一样:handleChatCompletions({model:'swe-2-medium'}),CLI 关闭时拿到
503 backend_unavailable(= 确实进了 special-agent 流,而不是 Cascade/legacy)。旧测试那种
裸对象 {name, modelUid}(backend 字段缺失) 的形状我单独驱动过:它走不到你的新分支(devin_local_only 只接受 gpt-5.6 luna/sol/terra 那族名字),所以在生产形状下才会落到 modelInfo.backend=special_agent —— 你的新测试现在钉的正是生产形状,这点改对了。
M3(可达性)— 已修,两个面各自可验证、互不抢路
| 请求 | DEVIN_CONNECT 开 |
DEVIN_CONNECT 关 + CLI 开 |
DEVIN_CONNECT 关 + CLI 关 |
|---|---|---|---|
swe-2-medium |
devin-connect(reason=devin_connect,赢过 special_agent) |
devin-acp/devin-print |
503 backend_unavailable |
gpt-5.6-luna |
devin-connect |
devin-acp(reason=devin_local_only) |
cascade |
resolveConnectSelector 里 swe-2 / swe-2.0 / swe-2-medium/high/max → mapped:true,快照 _count 120→123 与新增 3 行一致(devin-connect-catalog-drift.test.js 有一条"快照行数 == _count"的断言,绿)。另外我确认了:swe-2* 在 Cascade 面不会被 /v1/models 广告出来(与 swe-1.6 同族行为),所以没有"列出来但调不动"的新裂缝。M3 的"两个部署面共用 catalog id、不抢一条路径"这个回答我接受。
M4(写错了)— 已修
--model单通道:子进程 env 里不再有DEVIN_MODEL(全仓git grep DEVIN_MODEL只剩那条注释);--model参数仍在(master 本来就有)。- 默认仍是 print:CLI 开、
DEVIN_CLI_MODE不写时 →devin-print。我按你 review 里那条"print 对带 tools 的请求直接 400"驱动了真 handler:status=400 type=unsupported_tool_boundary,逐字对上。"描述漏写了.env里的DEVIN_CLI_MODE=acp"这个解释成立,而且现在有测试钉住 print/acp 两条分支。
M5(正则)— 已修,锚住了,我拿"会被劫持的那几个名字"打过
| 输入 | 现在 | 旧正则 |
|---|---|---|
gpt-5.60 / gpt-5-60 |
不命中(真 handler:400 Unsupported model) |
/gpt-5[.-]6/ 命中 |
gpt-5.6-luna-ultra / gpt-5.6-lunaish |
不命中 | /gpt-5[.-]6/ 命中 |
swe-2-0 / swe-2-pro / swe-2.5 |
不命中 | `/swe-2(?:[.-] |
gpt-5.6-luna / gpt-5-6-luna / gpt-5.6-sol / gpt-5.6-terra-high / gpt-5.6-luna-high-priority |
命中 devin_local_only |
— |
两点补充(都不是反对,是记账):① swe-2* 全族已被 M2 的短路接管,所以新正则里 swe-2 那半边是按设计不生效的 —— 你自己在注释里也写了;② sol/terra 目前到不了那条分支:chat.js:4039 的 if (!modelInfo) 400 Unsupported model 在 selectBackend 之前,而 MODELS 里只有 gpt-5.6-luna-*。也就是说 luna|sol|terra 里的 sol|terra 现在是给云端 catalog 合并进来的行预留的,本版无法驱动(我如实报"未验证",不当作缺陷)。
M6(默认放行)— 已修
DEVIN_PERMISSION_MODE=auto兜底整行删除:子进程 env 里DEVIN_PERMISSION_MODE不存在(上表实测)。你 checklist 的"新开关默认关"现在与代码一致。clientInfo.name默认回WindsurfAPI,冒充改成显式 opt-inDEVIN_ACP_CLIENT_NAME。这与 master 的默认值相同,所以对既有部署是零行为变更,新加的只是一个开关 —— 对 [Bug] Grok CLI "You are Grok ... released by xAI" self-ID 触发 Devin content policy (permission_denied) #223 那一族立场来说这是正确的方向。
方言那条按你说的降级成 openai_json_xml 我也核了:swe-2 / swe-2-medium / swe-2-0 都落在 openai_json_xml,与 test/tool-emulation.test.js:242 的断言一致。没有真实工具调用原文就不写 kimi_k2,这个处理是对的。
新发现(都在"小项"级,不阻塞)
N1 — swe-2 的别名表只加了一半,两个面不一致。 src/models.js:395-400 里你加了
swe-2.0 / swe-2-0 / swe2 / swe-2.0-medium / swe-2.0-high / swe-2.0-max,
但 src/devin-connect-models.js 的 SELECTOR_MAP 只收了 swe-2 / swe-2.0 / swe-2-medium/high/max。实测后果:
| 请求名 | DEVIN_CONNECT=1 面 |
说明 |
|---|---|---|
swe-2、swe-2.0 |
→ swe-2-medium ✅ |
|
swe-2-0、swe2、swe-2.0-medium、swe-2.0-high、swe-2.0-max |
mapped:false → 默认 STRICT_MODEL=1 下 400 model_not_found;STRICT_MODEL=0 下静默降级到 swe-1-6-slow |
这几个拼法是你这次刚加进 _lookup 的,所以在 Connect 面上"名字存在、却调不动" |
这条我在真 handler 上驱动过(DEVIN_CONNECT=1):swe-2 与 swe-2.0 都走过了 whitelist
(我这边没有 DEVIN_CONNECT token,所以停在 401 no session token,即"名字被接受"),
而 swe-2.0-high 停在
status=400 type=invalid_request_error
message: The model `swe-2.0-high` is not a valid model for this endpoint.
而 resolveModel('swe-2.0-high') 是有结果的(-> swe-2-high)。这一处差异就是 N1 的全部:
同一张 _lookup 里加进来的名字,在 Connect 面上一个能过一个不能过。
两个面的解析表本来就不同(Cascade 面 swe-2.0-high 解析成 swe-2-high,Connect 面不走这条),
93ae320 那条教训的形状就是这个:同一个名字在不同解析器里差异对待。要么把这几条补进
SELECTOR_MAP,要么别把它们写进 _lookup —— 别让它们只在一半面上成立。
N2 — 新开关没进文档,而且它是这个功能唯一的操作者入口。 DEVIN_ACP_CLIENT_NAME
全仓只出现在声明处(src/devin-acp.js:550),.env.example 里没有。这个仓库的
docs-consistency-guard 对开关台账是认真的(test/default-on-switch-registry.test.js
只扫 DEVIN_CONNECT_ / WINDSURFAPI_ / CASCADE_ 三个前缀,所以它逃过了这份台账 ——
和上一轮 DEVIN_PERMISSION_MODE 是同一个盲区)。盲区在我这边,我会单独修,
但麻烦你在 .env.example 的 Devin CLI 段补一行说明,否则没人知道这个开关存在。
N3 — 三条测试形状的记账(不影响结论,但别让它们被当成更强的证据):
① 负例 gpt-5.60 用的是手写对象 {name:'gpt-5.60', modelUid:'gpt-5-60'} —— 生产里它到不了
selectBackend(chat.js:4039 先 400 了),所以那条其实是纯正则单元测试,不是可达性测试;
② luna|sol|terra 里 sol/terra 本版不可达(同上);
③ devin_local_only 分支本身没有"非 SKU 名字不被劫持"的断言 —— 只有
swe-2 那一侧钉住了。要补的话,一条断言就够:{name:'gpt-5.60', modelUid:'gpt-5-60'} 且
CLI 打开时 flow === 'legacy'(我在真机器上量到的就是这个结果)。
下一步
没有阻塞项了,可以进合并流程。 建议顺序:
- N1 在本 PR 里顺手收掉(补
SELECTOR_MAP或撤掉多余的_lookup行,二选一,别留半张表)——
这是我唯一希望在本 PR 里改的东西,因为它是你这轮新引入的不一致。 - N2 补
.env.example一行;N3 可选。 - 你改完 ping 我,我会按那时的
origin/master重跑全量 + 这轮同一套驱动,再走
branch → git merge --ff-only → push。合并时按惯例更新src/dashboard/data/contributors.json。
再说一次结论的边界:我判"可合"针对的是这 7 个文件、这个 head。swe-2-medium/high/max
在 DEVIN_CONNECT 面上的真实可达性、以及 devin acp --model <key> 对这些 key 的接受度,
我在这台机器上没有 Devin CLI、也没有 live 账号,没有验证,也不假装验证过:
credits 6/9/12 我沿用你引的 docs.devin.ai 数字,没有再拉一次。如果这两条里有哪条后来被证伪,
那不是小项,是要重新判的。
User-visible: the tool-preamble ladder no longer builds the schema-compact tier it would discard. A padded \ diamond goes 11.357 -> 0.911 ms on the request (-92.0%) and 10.741 -> 0.455 ms on the ladder (-95.8%); twelve levels -82.0%/-89.7%. The ordinary path is NOT claimed as faster: a 2x2 factorial over arm position x file gives a position bias of +3.6% and a file effect of -0.6% that flips sign inside the arms, while the ladder is -9.3% and same-signed in both. Responses are byte-identical, pinned three independent ways (per-iteration sha256; 96 goldens unmoved plus 48 master-captured boundary rows; a new test against an unbounded reference across all 96 settings). Also: .github/PULL_REQUEST_TEMPLATE.md no longer hands contributors --test-force-exit. It was removed from the scripts in 939872b but left in the one file a contributor actually reads, which is why PR #266's author used it; a green run under that flag may have executed less than half of a file while exiting 0. Engineering: mutation specs 48 -> 49, mutations 576 -> 588, the new spec's 12 mutations all CAUGHT on the runner with all 12 anchors in new code; the 10 specs whose anchors live in the touched files all OK (cascade-metadata-egress 88/88, schema-ref-fanout-budget 19/19), guard-5 refusals 0. The long-standing reasoning-dedup-incremental survivor is closed as an equivalent mutant with a mutation that actually bites. No API breakage: 2xx bodies byte-identical, status codes untouched. ACU ^22 still default off. FREE_TIER_SELECTOR still swe-1-6-slow.
改了什么 / What changed
Cascade 对
gpt-5.6*和swe-2*会返回 403This model is only in Devin Local。在DEVIN_CLI_ENABLED=1时,这些模型改走本机devin acp(与 Devin Desktop 的 Devin Local 同一条路径)。SWE-2(medium / high / max)补进 catalog。Gemini 等 Cascade 模型不变。为什么 / Why
SWE-2 于 2026-09-10 上线 Devin Desktop / CLI(docs.devin.ai:
swe-2-medium/swe-2-high/swe-2-max),v3.9.31 catalog 没有这些 selector。gpt-5.6 同样是 Devin Local-only。Enterprise 账号没有 CLI HTTP 权限(
GetCliModelConfigs403),但 Desktop ACP 可用。实测:ACPinitialize的clientInfo.name必须是windsurf,且authenticate要带api_server_url(默认https://server.codeium.com),否则 team settings 403。--model/DEVIN_MODEL让 session 用请求的模型,而不是 CLI 默认。未改 DEVIN_CONNECT;未新增默认开的开关。
测试 / Testing
Node v22.23.2。跑过:
327 pass / 0 fail。
Live smoke(enterprise session + bundled
devin acp,DEVIN_CLI_ENABLED=1):POST /v1/chat/completionsmodel=swe-2→ HTTP 200pong(log:special-agent backend (devin-acp))model=gpt-5.6-luna→ HTTP 200pongvia ACPmodel=gemini-3-8-flash-low→ HTTP 200 via Cascade(未改道)未跑全量
npm run test:release(本机 Node 22)。Checklist
test/default-on-switch-registry.test.js台账 / no new default-on switchesDEVIN_CLI_ENABLED/DEVIN_CLI_MODE)clientInfo.name=windsurf),非 proto 字段号