← HowTo HT · 0001 · 2026-08-22
HOWTO / [AI 工具配置] · DSH × OpenRouter v1 · 2026 · AUG 22
GUIDE DEEPSEEK HARNESS 0.1.1-RC.2 MULTIMODAL VISION

DeepSeek Harness 接入
OpenRouter 的 Ox Alpha(含多模态)

在全新的 DeepSeek Harness 上接 stealth/ox-alpha 并且能读图。 改动量:界面点三下,手加一行。 不用改任何默认插件配置,不用建 cordis.patch.yml,不用禁用任何东西。
改动步骤
3
手写配置
1
实测环境
dsh 0.1.1-rc.2
pi-ai 0.82.1
视觉验证耗时
16s
TL;DR
  1. export OPENROUTER_API_KEY=... 进环境变量。
  2. npx @deepseek-ai/[email protected] web → Settings → Models → Add provider → openrouter → Customized settings → Add model,填 stealth/ox-alpha
  3. Open configuration file,给这条模型手加一行 input: [text, image],刷新页面即可读图。
§ 01 / STEPS

三步

1

Key 进环境变量

export OPENROUTER_API_KEY=sk-or-v1-你的key

pi-ai 内置 openrouter → OPENROUTER_API_KEY 映射,dsh 会自动认。界面上的 API key 框会变成只读的 "Provided by the launch environment"——一个字不用敲,密钥也不落盘

要持久化就放一个工作区之外的文件:

mkdir -p ~/.config/dsh-oxalpha
echo 'OPENROUTER_API_KEY=sk-or-v1-你的key' > ~/.config/dsh-oxalpha/env
chmod 600 ~/.config/dsh-oxalpha/env
别放工作区里

dsh 的 workspace-write 沙箱只限制写和执行,读不受限——agent 能直接读到,会话日志有带出去的风险。

2

起服务,界面点三下

npx @deepseek-ai/[email protected] web

首次进来点 Continue,再点 Configure later 跳过 DeepSeek 官方 key。然后:

Settings → Models → Add provider →openrouter → Customized settings → Add model,填 Model ID stealth/ox-alphaApply

dsh 界面里 openrouter provider 的 Customized settings 卡片,填入 stealth/ox-alpha 模型 ID
provider 卡片 · Settings → Models → openrouter → Customized settings

openrouter 是 pi-ai 内置目录里的 provider,Base URL 和协议自动继承,不用填。

别点 "Fetch available models"

对内置目录的 provider 它本地作答、不发网络请求,返回的是 pi-ai 打包的那份 276 个模型清单。ox-alpha 是 2026-08-20 才上架的,不在里面。手输就对了。

到这里文本对话已经能用,但还看不了图。

3

手加一行

点界面右上角 Open configuration file,给模型加 input

llm-pi-ai:
  providers:
    openrouter:
      models:
        - id: stealth/ox-alpha
          name: Ox Alpha
          contextWindow: 1048576        # 建议加:不写的话按兜底值 262144 算
          input: [ text, image ]        # ← 必须手加,界面上没有这个字段

不用重启,dsh 每次请求都重读配置,刷新页面即可。

这就是全部。整个 DSH_HOME 里只有这一个文件,其余全是 dsh 默认值。

§ 02 / WHY

为什么那一行省不掉

界面上没有模态开关,是官方明确的设计:

"A model you enter by hand is treated as text-only until it says otherwise, because nothing can ask an endpoint which modalities it accepts."

没办法去问一个端点"你收不收图片",所以只能由你声明。harness 会在图片发出去之前就拦截——这是个声明,不是检测,写错了只会在真正发请求时才炸。

漏了会看到两种报错,根因相同:

场景报错
让模型读图... does not declare image input
在已有图片的会话里切到它... does not accept image input, but this session already contains images

补完刷新页面即可,会话不用重建

三个容易误会的点:

视频传不进去。 OpenRouter 标称支持,但 pi-ai 的模态枚举只有 text | image

§ 03 / SOURCES

模态是怎么进到 DSH 里的

上面那 276 个内置模型,很多是带 input: [text, image] 的——凭什么它们不用手写,ox-alpha 就要?搞清楚这个,就知道该怎么批量导入了。DSH 这条链路上,模态信息有三个可能的来源:

来源一:pi-ai 的构建期快照

node_modules/@earendil-works/pi-ai/dist/models.generated.js 开头写着:

// This file is auto-generated by scripts/generate-models.ts
// Do not edit manually - run 'npm run generate-models' to update

再往下是 import values from "./data/openrouter.json"。也就是说,这是一份打包时冻结的 JSON 快照,37 个 provider、1109 个模型,每条带着 inputcontextWindowcompat 等等。问题就在"冻结"两个字:

数据源数量
pi-ai 内置的 openrouter 模型276
OpenRouter 线上实际有的421

差的 145 个里就包括 ox-alpha(2026-08-20 上架,快照做的时候还不存在)。快照不会自己更新,只有 pi-ai 发新版本才会带上新的一批。

来源二:dsh 的 "Fetch available models"

这个按钮看着像是去线上问,实际分两种情况,两种都拿不到模态

所以不管点哪种,input 都得你自己填。

来源三:OpenRouter 自己的 API(唯一可行的批量路径)

OpenRouter 的 /api/v1/models 是有完整模态信息的:

curl -s https://openrouter.ai/api/v1/models \
  | python3 -c "import json,sys; d=json.load(sys.stdin)['data']; \
      m=[x for x in d if x['id']=='stealth/ox-alpha'][0]; print(m['architecture'])"
{"modality": "text+image+video->text",
 "input_modalities": ["text", "image", "video"],
 "output_modalities": ["text"], "tokenizer": "Other"}

input_modalities 就是我们要的东西。所以正确做法是从这里拉,自己生成配置。仓库里的 gen-models.py 干的就是这件事:

python3 gen-models.py stealth/ox-alpha
llm-pi-ai:
  providers:
    openrouter:
      models:
        - id: stealth/ox-alpha
          name: Ox Alpha
          contextWindow: 1048576
          maxTokens: 131072
          input: [ text, image ]
          # 注意:OpenRouter 标称还支持 video,但 pi-ai 模态枚举只有 text/image,传不进去

可以一次生成多个,或者按能力批量筛:

python3 gen-models.py stealth/ox-alpha anthropic/claude-haiku-4.5
python3 gen-models.py --vision-free      # 所有免费且支持图像的(当前 12 个)

脚本做了三件手写容易漏的事:过滤掉 pi-ai 不支持的模态(video / file 会被丢弃并留注释)、给含冒号的模型名加引号Anthropic: Claude Haiku 4.5 不加引号会破坏 YAML)、带上线上的 contextWindow 和 maxTokens(不写的话按兜底值 262144 / 32768 算)。

为什么 harness 不干脆自己拉

因为模态在这套设计里是一个声明,不是一次探测。官方文档的原话:"Both fields state a claim about your endpoint rather than checking it."

自动拉看着方便,但它会把"OpenRouter 说这模型支持 video"这种信息也带进来,而 pi-ai 根本传不了 video——于是变成一个多声明:请求发出去、用户消息已落库、服务端拒绝,会话卡在反复重发上。少声明只是一句报错,多声明是会话卡死。

所以这条线上,生成脚本的输出仍然是给你审一眼再贴进去的草稿,不是自动同步。

§ 04 / VERIFY

验证

用一张内容明确、能对答案的图。这里用《牛来》的一帧剧照:

测试用剧照:两只动物在画面中,天空呈夕阳色
测试图 · liulai/images/frames/名场面_已力竭_15.jpg

放进工作区,界面里选中 Ox Alpha,然后问:

用 read_image 看 niulai-test.jpg,回答四件事,一行一个:
1) 画面里一共有几只动物 2) 体型大的那只头上的角是什么颜色
3) 右边小的那只是什么颜色 4) 天空是什么颜色

Ox Alpha 的作答截图
验证结果 · 四项全中,16 秒

正确答案:2 只 / 紫色 / 黄色 / 黄橙色夕阳。实测四项全中,16 秒。

判断标准

是它有没有真的调用 read_image。第 3 步没生效时模型不会放弃,它会改去调 OCR——那样图上的文字还能读出来,但数量、颜色这类纯视觉信息答不出来。所以问题里一定要有颜色和数量。

§ 05 / OPTIONAL

可选:让思考档位可以选

上面的配置里,模型选择器没有 Low / High / Max 档位。因为手输的模型没声明思考能力,pi-ai 就当它不会思考。模型实际还在思考(ox-alpha 关不掉),只是按自己的默认档 max 跑,你控制不了。

多数情况这样就挺好。想要选择器,加两处:

llm-pi-ai:
  providers:
    openrouter:
      reasoning: max                    # ← 和下面成对出现,不能只加一个
      models:
        - id: stealth/ox-alpha
          name: Ox Alpha
          contextWindow: 1048576
          maxTokens: 65536              # 思考 token 也算在这个预算里
          input: [ text, image ]
          reasoningEfforts:
            low: low
            high: high
            max: max                    # 故意不写 off

三个约束,违反任何一条都会报错:

  1. reasoning: max 不能省。 声明了 reasoningEfforts,pi-ai 就认为该模型会思考,每次请求都要带档位;某次没带时它会发 reasoning: {effort: "none"},而 ox-alpha 的思考强制开启,直接 400 Reasoning is mandatory...。dsh 内部有生成会话标题这类后台调用不走界面,所以这个错会冷不丁蹦出来。
  2. reasoningEfforts 里不能有 off(写成 off: 留空也不行),同样的 400。
  3. maxTokens 要给足。 实测 max_tokens: 60 时 high 档 60 个 token 全被思考吃光,正文一个字没输出,接口还返回 200。端点上限 131072。

这套只适合"路由下只有 ox-alpha"。 reasoning: 是路由级的,会强加给该路由每个模型。实测加两个目录模型后两个都起不来:UNSUPPORTED_REASONING_EFFORT。要混用多个模型,就把 reasoning:reasoningEfforts 一起去掉,回到上面那份最简配置。

默认档填什么(一道编码题,单次测量):

档位耗时思考字数输出 token
low5.6s0145
high4.8s0146
max11.0s812368

low / high 在这道题上压根没触发思考。模型自己会判断该不该想,默认拉到 max 即可。

档位是按会话记的。改了 reasoning: 要开新会话才看得到新默认值;老会话保留它自己的档位,这不是配置没生效。

§ 06 / OPTIONAL

可选:超时与重试

dsh 的几个默认值对 ox-alpha 偏紧,长任务容易撞上。这些和 Claude Code 的 CLAUDE_* / BASH_* 环境变量是两套东西,dsh 一个都不认,要写进 dsh 自己的配置。

settings.yaml 的 provider 上:

llm-pi-ai:
  providers:
    openrouter:
      streamIdleTimeoutMs: 900000   # 默认 300000(5分钟)
      retryPolicy:
        mode: normal
        maxRetries: 8               # 默认 5
        backoff: { initialDelayMs: 500, maxDelayMs: 30000, jitterRatio: 0.1 }
      models:
        - id: stealth/ox-alpha
          maxTokens: 131072         # 端点上限,避免长输出被截
          # ...其余同上

cordis.patch.yml 里:

- id: bash-sandbox
  config:
    timeoutMs: 600000               # 默认 60000(60秒)

最该改的是 bash-sandbox.timeoutMs 基线值是 60 秒,实测跑 sleep 90 会在 60 秒被 SIGTERM 杀掉:

超时被终止,没有输出 —— 超时时长 60000ms,终止方式 SIGTERM

提到 600000 之后同一条命令正常跑完。streamIdleTimeoutMs 管的是"模型安静多久算超时"。ox-alpha 强制思考,max 档 + 长上下文确实可能长时间不吐字——用 curl 测视觉时,默认档就撞过 2 分钟不返回。

thinkingBudgets 别配。 pi-ai 有这个字段,但只有 anthropic-messages / google-* / bedrock 这几条线会读它;openrouter 线走的 openai-completions.js 根本没引用,配了是死配置。ox-alpha 的思考预算只能通过 reasoningEfforts 档位间接控制。

输出长度不用调。 dsh 的 spill-policy.maxInlineBytes 默认 50000,但超出后是把全文存盘、只给模型看头尾预览加取回路径,不是丢弃。跟"日志被截断"不是一回事。

§ 07 / ERRORS

报错对照表

报错原因修法
does not declare image input该模型条目缺 input第 3 步。注意模态不跨模型继承
does not accept image input, but this session already contains images同上,切模型时出现同上,补完刷新页面
模型改去调 OCR 而不是 read_imageinput 没生效检查缩进,确认加在 ox-alpha 那条下面
UNKNOWN_MODEL模型不在 models 列表里models 是替换不是追加,要用的都得列
Reasoning is mandatory...(400)请求发了 effort: "none"reasoning: max,或把 reasoningEfforts 一起删掉
UNSUPPORTED_REASONING_EFFORT路由级 reasoning: 强加给了不支持的模型路由下不止 ox 时,去掉 reasoning:reasoningEfforts
MISSING_CREDENTIALOPENROUTER_API_KEY 没解析到确认启动服务那个 shell 里有这个变量
402 requires more creditsOpenRouter 余额不足ox-alpha 免费不受影响,别的模型要充值
404 No endpoints found该模型 OpenRouter 已下架换一个,内置目录可能比线上滞后
端口被占3080 已占用dsh web --port 3090
装依赖像卡死,node_modules 一直空的npm 在解析依赖树,正常等(约 9 分钟),或用 pnpm
§ 08 / SAFETY

安全提醒

  • ox-alpha 是 stealth 模型,由未公开身份的第三方开发运营,提示词和回复由该厂商留存(按 OpenRouter 条款不用于训练)。别喂客户数据、密钥、私有代码。
  • 保持 dsh 默认的 workspace-write 沙箱。有些 demo 写 permission.defaultPreset: danger-full-access,那会让 agent 读写工作区之外的任何文件,别在真项目里这么干。
  • 密钥别放工作区——沙箱只限制写和执行,读不受限,agent 能直接读到。
§ 09 / REFS

参考

DeepSeek Harness — GitHub 官方 Configure models 指南 OpenRouter — Ox Alpha Stealth Model Terms