dsh-ark-coding-provider
火山方舟 Coding Plan 的 DeepSeek Harness LLM 提供商插件(非官方社区集成)。
仿照 @mars-sea/dsh-commandcode-provider
的形态做的:注册一个提供商路由、在 Models 页挂一张属于自己的卡片、在设置里开一个
同名分区,把模型目录、容量事实和密钥管理都放在那里。
这是社区集成,不是火山引擎官方产品。你需要自己的火山方舟账号与 Coding Plan 订阅,
并遵守火山引擎的服务条款。
截图
它给你什么
- 一个提供商路由
ark-coding —— 直接对着 <span>https://ark.cn-beijing.volces.com/api/coding/v3</span>
的 OpenAI 兼容端点说话,/chat/completions + SSE 流式。
- 12 个模型,容量事实是实测出来的 —— 上下文窗口、单次输出上限、是否吃图、
接受哪些
reasoning_effort 档位,全部逐项探过(见下表)。
- 设置页「设置 → 火山方舟」 —— 账户管理、登录向导、逐模型可见性、连接参数、上下文覆盖。
- Models 页的提供商卡片 —— 只读的账户现状:正在用哪个账户、额度来源、
没配好该去哪。插件贡献的这张卡片上没有密钥输入框、也没有「测试连接」按钮;
密钥在设置页的账户行里填。官方 Models 页自带的密钥编辑器(宿主渲染,插件管不着)
写的是顶层
ARK_CODING_API_KEY,一个账户都没有时它会被当成兜底账户读进来
(见「配置密钥」)。
- 模型选择器注解 —— 上下文长度与图像能力直接来自适配器上报的事实;文案是
语言无关的短形式(
厂商 · 1024K ctx),因为这段文字出现在 harness 共享的
模型选择器里,那里没有插件侧的语言席位。
- 宿主侧的模型发现 —— 插件注册了一个 discovery(
index.js 的 createDiscovery()):
被调用时用一次性密钥真实打一次 Coding Plan 端点,成功则回传这 12 个模型的
容量事实;密钥只在这一次请求里存在,宿主不存储它,浏览器永远不接触方舟。
它由宿主 Models 卡片上的模型拉取动作(Fetch available models)触发,
不是插件设置页里的按钮。
- 密钥不进配置文件 —— 走本机凭据服务写入,页面只看到"配没配"。
- 内置 arkcli —— 不用你自己装:设置页里一个按钮把官方 CLI
@volcengine/ark-cli 装进插件自己的目录,
然后用它拉官方模型目录和真实套餐额度。装的是钉住的
@volcengine/ark-cli@1.0.39(实测通过的版本),不是 latest:额度、目录、
聊天取 key 三条链全都建立在它的输出形状上,装 latest 等于每次点安装都默默绑定
当天的最新版。
- 两种账户,可混用 —— 每个账户要么是「CLI 登录(SSO)」,要么是「API Key」;
添加时可以给它起个名字(不填也行)。某个账户打满就自动轮到下一个。
- 真实额度,不掺本机数字 —— 已登录的 CLI 账户(或一对 AK/SK)能查到
5 小时 / 本周 / 本月的已用百分比与刷新时间。左下角卡片只放日常要盯的
5 小时与本周两档、再没有第三行(账户是谁不影响你要盯什么,账户名只在
点开的详情面板和设置页里出现),点一下展开详情:每个账户的登录情况 + 三档额度。
卡片行距也按侧边栏的尺寸调紧了:展开态行间 6px、窗口内 4px;侧边栏折叠成
窄轨道(约 56px,卡片只剩两行百分比)时压到行间 5px、窗口内 2px。
查不到真实额度时卡片不显示(一张只写着"未配置"的常驻卡片没有信息量),
绑上 CLI 账户或 AK/SK 后它自己就出现 —— 不用谁再去手动打开一次。
没有本机 token 统计:那个数字不是套餐额度,曾经摆在旁边只会让人误读。
百分比在 10% 以下保留一位小数(
0.4%)—— 这个账号日常就落在 0.1%~1%,
一律取整会让卡片长期写着 0%,看着像没用量。重置时间照抄控制面给的那一刻,
不按日历推算:实测这个账号的 monthly 重置在 15 日 23:59:59,不是 1 号。
模型目录(实测 2026-10-15)
单位是 token。模态一栏里 图像 表示该模型接受图片输入(视频/音频输入在 DSH 的模态
词汇里没有对应值,因此不计入)。
| 模型 id | 上下文 | 输出上限 | 模态 | 思考档位 | 厂商 |
|---|
doubao-seed-evolving | 1M | 256K | 文本 · 图像 | off/minimal/low/medium/high/max | Doubao |
doubao-seed-2.1-pro | 1M | 256K | 文本 · 图像 | off/minimal/low/medium/high/max | Doubao |
doubao-seed-2.1-lite | 1M | 256K | 文本 · 图像 | off/minimal/low/medium/high/max | Doubao |
doubao-seed-2.0-mini | 256K | 128K | 文本 · 图像 | off/minimal/low/medium/high/max | Doubao |
minimax-m3 | 1M | 128K | 文本 · 图像 | off/minimal/low/medium/high/max | MiniMax |
glm-5.3 | 1M | 128K | 文本 | low/medium/high/max | Zhipu |
glm-5.3-flash | 1M | 128K | 文本 · 图像 | off/minimal/low/medium/high/max | Zhipu |
deepseek-v4.1-flash | 1M | 384K | 文本 · 图像 | off/minimal/low/medium/high/max | DeepSeek |
deepseek-v4-flash | 1M | 384K | 文本 | off/minimal/low/medium/high/max | DeepSeek |
deepseek-v4-pro | 1M | 384K | 文本 | off/minimal/low/medium/high/max | DeepSeek |
kimi-k2.8-preview | 1M | 128K | 文本 · 图像 | off/minimal/low/medium/high/max | Moonshot |
kimi-k3 | 1M | 128K | 文本 · 图像 | off/minimal/low/medium/high/max | Moonshot |
三个反直觉的地方(都是实测结论,不是猜的)
glm-5.3-flash 其实能吃图。Ark 自己的 /models 把它记成 in=[text],
但给它一张四象限测试图,它准确答出 blue, yellow, green, magenta。所以这里按支持图像处理。
glm-5.3 不能吃图,也不能关思考。图片请求被明确拒绝
(Model only support text input),thinking.type=disabled 与 reasoning_effort=none/minimal
同样被拒。它的可用档位是 low/medium/high/max(off/minimal 被拿掉,其余照收)。
minimax-m3 回传的 tool_calls[].type 是 functionfunction(服务端序列化缺陷),
原样回灌第二轮工具调用会 400。适配器重建请求时强制写 type: "function",
多轮工具调用因此可用。
deepseek-v4-flash 与 deepseek-v4-pro 按纯文本处理:前者带图三次都在 180 秒内无响应,
后者收图后回答"看不到图片"。DSH 的模态声明宁可少给也不能错给 —— 少给只是在附图片前
拒绝并点名模型,错给会让一个不可完成的请求在消息落盘之后才失败。
安装
# 打包
cd ark-coding-provider && npm pack # 产出 dsh-ark-coding-provider-<version>.tgz
# 装进 profile(把路径换成上面那个 tarball)
dsh plugin --profile web add /path/to/dsh-ark-coding-provider-<version>.tgz
本地路径要用 tarball,不要用目录。 用目录安装时 pnpm 会建符号链接,
而 DSH 的运行时拦截只为「profile 树内」的包解析 harness 包(@deepseek-ai/dsh-llm 等),
链接到 workspace 的包会以真实路径在 profile 树之外被导入,于是所有 harness 包
ERR_MODULE_NOT_FOUND,表现为 failed to import。
装完重启一次 dsh(替换已加载的包需要新的模块世代)。
配置密钥
最省事的路径是设置页:设置 → 火山方舟 → 「账户」卡片,新建一个「API Key」类型的
账户,在那一行里的密钥输入框粘贴后保存(没有名为「API 密钥」的独立卡片)。
密钥写进本机凭据服务,页面只显示"已配置"与来源,永不回传值。
一个账户都没有时,顶层字段会作为兜底账户被读。 官方 Models 页会给每个可配置的
provider 渲染一把密钥编辑器,它写的引用名是从 provider 名推出来的
ARK_CODING_API_KEY(ark-coding → ARK_CODING_API_KEY,插件无法控制)。只要
accounts 是空的,插件就把顶层 apiKeyEnv 当成一个 id 为 default、显示名
「默认账户」的 API Key 账户,顶层 apiKey 则是它的字面量密钥。这条兜底专门用来接住
上面那把编辑器:不接的话,用户粘了 key、卡片显示"已配置",聊天却报"一个可用账户都没有"。
accounts 非空时这条兜底整个不存在,顶层字段不再被任何人读。
- 它只会是 API Key 账户(走不了 CLI 登录),也不写进
accounts —— 所以在设置页
删掉「默认账户」那一行没有持久效果,只要 apiKeyEnv 还在,下一个请求它就会回来。
- 要真正关掉它:把
apiKeyEnv 显式设成空串(默认值是 ARK_CODING_API_KEY,
不设就是开着)。写成 MY-KEY 这种不合法的凭据引用名同样不会产生兜底账户。
- 在设置页保存账户列表时,空的
accounts 会被整份写回(老配置的迁移路径)——
兜底账户那时就变成 accounts 里的一个普通账户,不再随顶层字段出现或消失。
也可以走环境变量(默认引用名 ARK_CODING_API_KEY):
export ARK_CODING_API_KEY="ark-..."
密钥优先级(每个账户各自解析):账户里的字面量 apiKey > 凭据服务 > 启动环境 / .env。
凭据引用名不合法(文法 ^[A-Za-z_][A-Za-z0-9_]*$)时跳过凭据服务,直接落到环境变量;
顶层 apiKey 只在上面的兜底账户里生效,不会补给 accounts 里的其它账户。
账户:两种登录方式
账户列表是账户的主要真源(配置里的 accounts)。每个账户有三样东西:类型、名字
(可选)、以及它自己的凭据来源。
「默认账户」不是配置里的账户,是 accounts 为空时的兜底。 它由顶层
apiKeyEnv(凭据引用)+ apiKey(字面量,可选)派生:id default、显示名
「默认账户」、类型 API Key。它没法走 CLI 登录,也不写进 accounts,所以删掉它之后
只要 apiKeyEnv 还在就会自己冒回来,真正关掉它得把 apiKeyEnv 设成空串。
accounts 非空时它完全不出现。保留这条路径是因为它是官方 Models 页密钥编辑器唯一
能落地的地方(详见上面「配置密钥」),代价就是这个"删不掉的默认账户"。
CLI 登录(推荐)
一次 SSO 登录同时解决三件事:聊天用的 API Key、真实套餐额度、官方模型目录。
登录一次之后插件会自己从 arkcli 的 profile 里取 key,不需要你去控制台手抄。
点「添加账户」→ 选「CLI 登录」→(可选)填个名字 → 「开始登录」,然后:
- 页面给出一个授权 URL。在任意设备的浏览器里打开它,完成火山引擎登录。
- 登录完成后浏览器会跳到一个
http://127.0.0.1:<端口>/… 地址,并且打不开 ——
这是正常的,那个端口开在跑 DSH 的服务器上,不在你的电脑上。
- 把浏览器地址栏里那条完整的地址复制下来,粘回设置页的输入框,提交。
插件会在服务器上把这条请求转发给 arkcli 的回环监听,登录随即完成。
每一步都在设置页里有提示,不需要你自己敲命令。如果你有办法把那个端口打通
(比如 SSH 隧道),登录也会自己完成 —— 向导在轮询,看到成功就会自动跳过去。
凭据是有时效的,到期要重新登录。 设置页会直接告诉你到哪天:插件读 arkcli
落盘的 profile expires_at(<账户 HOME>/.arkcli/config.yaml)换算成日期显示在账户行上,
快到期的会提前提示、过期了会标成需要重新登录。
为什么不写死"N 天":这是服务端策略给的值,会随租户变。实测本机一个 2026-10-16 登录的
Coding Plan 账户给的是 2026-12-15 23:59:59(UTC+8),而不是常被提到的 14 天。
与其把一个可能不对的天数印在界面上,不如把算出来的具体日期给他。auth status 里那个
sts_expires_at_ms 只有 15 分钟(arkcli 自己会刷新),不能拿来当"何时重登"用。
多账户怎么隔离的:arkcli 的状态目录写死在 $HOME/.arkcli,没有环境变量能改
(它的官方文档明确说了这点)。所以插件给每个 CLI 账户一个独立的 HOME
(<DSH_HOME>/ark-coding/accounts/<账户 id>),用 HOME= 起子进程。这样同一台机器上
可以同时持有多个火山引擎账号,互不覆盖。
账户也可以显式指定 home,但它必须位于 <DSH_HOME>/ark-coding 之下,越界时插件
在用到它的地方直接抛错、那个账户不可用。原因是那条路径会成为 root 子进程的 HOME,
arkcli 会在它下面写 .arkcli/ 凭据:accounts 是浏览器可写的易变字段,若允许任意路径,
一次配置写入就能让 root 往任意目录写文件。日常根本用不到自定义 HOME —— 多账户隔离
靠的就是"每个账户一个派生目录"。
API Key
从控制台复制一把方舟 API Key 粘进来。它能聊天,但查不到套餐额度、也拉不到官方模型目录 ——
这不是插件的限制,是火山接口本身的划分:额度与目录在控制面,只认 SSO 身份(或 AK/SK),
API Key 只是数据面凭据。所以要看到用量,至少要有一个 CLI 登录的账户。
额度怎么看
- 有已登录的 CLI 账户 → 显示真实百分比(5 小时 / 本周 / 本月)+ 刷新时间。
Coding Plan 的上游只给百分比、不给绝对值,所以这里不会编一个"还剩多少 token"出来。
- 一个都没有 → 卡片(左下角)按
sidebarQuota 的判据不显示,设置页里照实说"查不到",
不编数字、也不退回任何本机记账(那套机制已删除,理由见上)。
- 另外还支持一对 AK/SK 作为备选来源(设置页「用量与显示」卡片里的
accessKeyId + Secret 引用),走 Volcengine V4
签名直接打控制面。仅在没有任何 CLI 账户可用时才会去用它。
官方模型目录(arkcli)
设置页的模型卡片会把 arkcli 拉到的官方目录并排显示:上下文、最大输入/输出、输入模态、
思考支持、版本、生命周期状态。
它是对照,不是覆盖。 本插件内置那 12 行的容量与模态是直接打 Coding Plan 端点实测
出来的,而 arkcli 给的是方舟通用目录的元数据,两者本来就可能不一致 ——
已知的真实分歧就写在上面那张表的下面(glm-5.3-flash)。所以插件把官方值挂在旁边、
把分歧点标出来,而不是偷偷用较不可靠的来源推翻实测值。
官方目录里如果出现了这 12 个之外的模型,卡片会提示「官方目录里还有 N 个模型不在本套餐列表中」,
这是"官方又发新模型了"的信号。
配置项
| 字段 | 默认 | 说明 |
|---|
apiKeyEnv | ARK_CODING_API_KEY | 只在 accounts 为空时被读取:作为兜底账户(id default)的凭据引用,也是官方 Models 页密钥编辑器写的那个引用名。显式设成空串即关闭这条兜底;引用名不合法(如 MY-KEY)同样不产生兜底账户 |
apiKey | — | 只在 accounts 为空时被读取:作为兜底账户的字面量密钥(带 secret 角色,永不进设置描述)。账户自己的字面量密钥写在账户对象的 apiKey 上,不会补给其它账户 |
apiBase | Coding Plan v3 端点 | 换端点/代理时改这里 |
defaultMaxTokens | 65536 | 未指定输出上限时的请求值,会被逐模型上限夹住 |
requestTimeoutMs | 300000 | 首字节超时 |
streamIdleTimeoutMs | 300000 | 流空闲超时 |
visibleModels | [] | 允许出现在选择器里的模型;空 = 全部 |
modelVisibility | {} | 逐模型开关,优先于 visibleModels |
contextWindowOverrides | {} | 逐模型覆盖上下文长度,上游改容量时不用等插件更新 |
accounts | [] | 账户列表:{id, label, type, apiKeyEnv, apiKey, home}。type 是 apikey / cli;home 只有 CLI 账户用,且必须位于 <DSH_HOME>/ark-coding 之下,越界时插件在用到它的地方直接抛错、那个账户不可用。重复的 id 只保留第一个(同名账户会共用同一个 HOME 与同一组派生缓存)。空 = 回到顶层兜底账户(见上两行) |
activeAccount | '' | 偏好的账户 id,优先用它 |
accessKeyId | '' | AK/SK 备选来源的 AccessKey ID(不是秘密,明文配);留空 = 不用这条路 |
secretAccessKeyEnv | ARK_ACCESS_KEY_SECRET | AK/SK 的 Secret 走这个凭据引用(role: credential-ref),值写凭据服务、不进配置文件 |
region | cn-beijing |
accounts 为空时,能聊的账户就只剩顶层兜底那一个(apiKeyEnv 非空才有;见上)。
它不写进 accounts,所以设置页删掉它并不会让它消失 —— 只要 apiKeyEnv 还在,
下一个请求它就会回来;要关掉就把 apiKeyEnv 设成空串。那时设置页才会显示
"没有账户"的空状态并引导你新建。
除顶层 apiKey(secret 角色,只随组合配置文件落地)外都是易变字段:设置页保存后
加载器原地提交,下一个请求就生效,不需要重启。
工作区里的文件
| 文件 | 作用 |
|---|
index.js | 宿主半边:Config、OpenAI 兼容适配器、模型目录投影、模型发现、Remote 端点、arkcli 编排 |
models.js | 12 个模型的实测目录 + 官方目录对照(annotateModels) |
accounts.js | 账户归一化(apikey / cli 两种类型、accounts 为空时的兜底账户、重复 id 去重)、账户 id 清洗的唯一实现(arkcli.js re-export 这一份)、轮换标记与排序、侧边栏卡片的三态判据 |
usage.js | 套餐额度的备选来源:AK/SK 的 Volcengine V4 签名与 GetCodingPlanUsage 解析 |
arkcli.js | arkcli 桥:探测 / 一键安装(钉住 1.0.39)/ 取额度 / 取目录 / 无浏览器登录编排 |
client.js | 浏览器半边:设置页 + Models 页卡片 + 侧边栏用量,只用主题 token,不引入任何 Client 包 |
cordis.patch.yml | bundle 层:插入 llm-ark-coding 条目 |
icon.svg / locale/*.json | 插件卡片显示元数据 |
请求是怎么走的
DSH 会话历史 ──► buildMessages() ──► OpenAI 兼容 messages
│ · system/developer 并成 system
│ · assistant 只回放可见文本 + tool_calls
│ · tool_call 的 type 强制成 "function"
▼
POST {apiBase}/chat/completions (stream + stream_options.include_usage)
│
▼
SSE 帧 ──► StreamChunk(block-start / text-delta / reasoning-delta /
tool-call-delta / block-end / usage / finish)
失败会归类成 DSH 的稳定失败码(AUTH / RATE_LIMIT / SERVER / TIMEOUT /
TRANSPORT / CONTEXT_WINDOW_EXCEEDED / QUOTA / EMPTY_RESPONSE / PROVIDER_ERROR),
由 dsh-llm-retry 按策略决定要不要重试。其中 PROVIDER_ERROR 承接的是流内
报出的、不属于额度类的上游错误。
图片:上层标了 offloaded 的先降级成占位文本;剩下的读成请求版(长边 1568、
编码 ≤1 MiB)并内联为 data URL。单次请求图片总量超过 20 MiB 时按 DSH 的契约抛
IMAGE_OFFLOAD_REQUIRED,让上层卸载最旧的几张再重试,而不是把一个注定超限的请求发出去。
验证状态
| 套件 | 结果 | 需要什么 |
|---|
tests/usage.test.mjs | 42/42 | 不需要密钥;末尾一条要网络(真实控制面) |
tests/arkcli.test.mjs | 174/174 | 不需要密钥、不需要网络(起本地假 arkcli / sleep 做进程实验) |
tests/client.test.mjs | 167/167 | 不需要密钥、不需要网络(内存渲染 + 自建 CSS 解析器) |
tests/host.test.mjs | 80/80 | 不需要密钥、不需要网络;需要 harness 包在场(跑 apply() 与适配器本体) |
tests/stream.test.mjs | 9/9 | 喂假 SSE:不需要密钥、不需要网络;需要 harness 包在场 |
tests/adapter.test.mjs | 需要真实 key 才跑(否则打印 SKIP 并 exit 0) | 一把真实 key + 网络(打真实端点) |
合计 472 项;上面五套在 npm test 里默认全跑(adapter 无密钥时明确跳过,不会假红)。
- 宿主侧(不需要密钥):V4 签名的规范化请求结构、额度窗口顺序与百分比夹取、账户归一化
(
accounts 为空时的兜底账户、重复 id 去重、CLI 账户、id 清洗),以及侧边栏卡片的三态判据
(auto 有绑定才显示 / on / off / 无法识别的档位退回 auto)。
这一段本身不碰网络 —— 唯一例外是 usage.test.mjs 末尾那条真实打控制面的可达性断言:
它用一对假 AK/SK 发真请求,期望被服务端按鉴权拒绝(以此证明签名头与请求结构被接受)。
因此这一套需要网络,而且网络故障时上游返回的错误文本可能不匹配
/AccessKey|Signature/,会把这条误报成失败(不是插件坏了)。
- 宿主适配器本体离线(
host.test.mjs):12 个模型的目录事实与思考档位、请求体的
max_tokens 夹取与 resolveModel 的默认上限夹取、失败码映射、图片字节预算与
IMAGE_OFFLOAD_REQUIRED 的形状、options.system 是否进请求体、Retry-After 的边界
(0、过去的 HTTP-date、畸形巨值)、脏 token 计数被整形为非负安全整数、SSE 的七条边界
(末帧无换行、没有终止标记的截断、[DONE] 之后的数据、字符串错误帧、裸断连被包成
TRANSPORT、单行上限、空闲超时里 cancel() 拒绝不留未处理 rejection)、账户兜底三态与
重复 id 去重、显式 home 越界被拒、派生缓存键、目录/额度在途去重、arkcliPath 接线、
settings.configure({ auto: false }),以及宿主↔客户端八个端点描述符逐一对齐。
以前这些只存在于需要真实密钥的 adapter 套件里,默认 npm test 对 provider 本体
等于零覆盖;现在不需要密钥也会跑。
- arkcli 桥离线:路径与 id 清洗(与
accounts.js 共用同一份实现:白名单
[A-Za-z0-9._-]、去前导点、截断 64 字符、空值回退 default)、JSON 信封解析
(含"错误信封在 stderr 上")、子进程最小环境(不继承宿主的 ARK_API_KEY)、
超时击杀、按进程组杀掉被 execFileSync 连带起来的孙子进程、凭据到期时间解析、
usage plan / models search / profile apikey get 归一化、登录编排全流程。
- 客户端离线:三个席位注册与渲染、账户增删改、登录向导各状态、
"没有账户"的空状态、类型选择说明、凭据到期提示(未来/7 天内/已过期/未知)、
目录分歧标注、provider 卡片不再有 key 输入框,以及用量卡片的三条新行为:
没绑定就完全不渲染、只画 5 小时与本周(月额度不出现)、
点开后详情里有登录情况与三档额度;还有两条 CSS 回归:
品牌底上不许出现写死的白色前景(深色主题的品牌色是近白色)、
卡片行距保持紧凑(展开态
gap:6px / 窗口内 4px;窄轨道 5px / 2px)。
卡片里必须一行账户信息都没有:akc-sideMeta 在卡片上为 0 个节点。
假上下文忠实模拟 cordis 的注入守卫;假的 $mount 也照真的一样校验贡献(只收 strict codec)。
- 流式 chunk 契约:用 DSH 自己的
isJsonValue 校验每一块,覆盖文本 / 思考 /
工具调用三条路径 —— 这条就是"输出出来后立刻失败"那个 bug 的回归测试。
跑测试:
npm test # 四套不需要密钥的测试;其中 usage 末尾一条要网络
ARK_CODING_TEST_KEY=ark-... npm test # 再加上打真实端点的适配器测试
踩过的坑(注释里也写了)
-
远端命名空间必须"谁声明、谁读取"。在 ctx.inject(['remote.settings'], cb) 的回调里读
cb.remote.credentials,cordis 会抛 cannot get property "credentials" without inject;
这个异常发生在注入回调内部,外层的 try/catch 抓不到,页面就永远停在"凭据服务未挂载"。
三个命名空间各自注入、各自读取。(对应用户 2026-10-16 实测报的那条错。)
-
remote.credentials 的契约是数组 + 信封:describe(refs: string[]) 返回
RemoteResult<Record<ref, CredentialInfo>>,set/unset 返回 RemoteResult<void>。
传单个字符串、或不解 ok/value 信封,都会静默拿到错误结果。
-
Node 的 fetch 会丢掉 Host 头。Host 在 Fetch 规范里是 forbidden header name,
Node 24 的 undici 直接静默忽略你设的值。而 arkcli 的回环回调监听会校验 Host,
头不对就是 invalid host、登录永远完不成。所以转发回调用的是 node:http.request,
它的头能原样落到 socket 上。(这个坑是写测试时对着真实二进制量出来的,不是推理出来的。)
-
回环监听只认 Host: 127.0.0.1:<那个端口>。逐条试过:localhost:<port>、裸
127.0.0.1、localhost、[::1]:<port>、端口差一个数字、大小写不同,全部 invalid host。
代码以 127.0.0.1:<port> 为准,并在真收到 invalid host 时自动用 localhost 再试一次,
免得将来某个版本改了校验就把功能锁死。
-
arkcli 的错误信封打在 stderr 上。它自己的约定是「stdout 只放结构化结果,
错误与解释走 stderr」。只看 stdout 的话,未登录时拿到的"错误信息"会是 stderr 的最后一行,
也就是一个孤零零的 }。宿主两个流都会解析。
-
arkcli 没有 ARKCLI_HOME,状态目录写死 $HOME/.arkcli。多账户隔离只能靠
换 HOME 起子进程(每个 CLI 账户一个),这也是插件创建
<DSH_HOME>/ark-coding/accounts/<id> 的原因。
-
1.0.39 的 --no-browser 不是两段式。它会在 127.0.0.1:<随机端口> 上起监听并一直等,
既不读 stdin,也不写 .sso-pending.json,所以 --code 那条路在这个版本上不可用
(包内文档描述的两段式流程与实际行为不一致 —— 以实测为准)。
-
客户端只收 mode: 'strict' 的参数 codec。协议里还有个 { mode: 'src-json' }(宿主网关
内部自己就用它,宿主侧也照收),但浏览器的贡献校验会当场拒掉,抛
field "input" has no strict codec —— 而它抛在整个 $mount 里,于是八个端点一起失效、
侧边栏永远停在 Loading…。不碰网络的测试全绿、真机全挂,只有真开一次页面才看得见。
现在两半都用 strict codec(create() 返回一个恒真的宽松解析器),并且把假的 $mount
改成会做同样的校验。
许可
MIT。非官方集成,与火山引擎无关。