Hermes Studio 免密钥 AI 供给OpenCode Free Provider 的架构、初始化与路由机制深度解析【免费下载链接】hermes-studioEkko Studio is a local-first AI workspace for multi-agent chat, coding, and visual workflows, available on desktop and the web.项目地址: https://gitcode.com/gh_mirrors/he/hermes-studio本文基于仓库文档 docs/opencode-free.md 并结合 packages/server 下的实际实现展开讲解 Hermes StudioEkko Studio如何内置一个无需 API Key、无需自定义 provider 配置的原生免费模型提供商opencode-free包括其后台初始化状态机、模型目录获取与缓存、会话 API 路由规则以及对 Scoped Coding Agents 的免上游密钥支持。读完你将掌握该免费 provider 的完整工作原理、关键参数超时、重试、刷新周期与可验证的测试命令能够在实际使用中判断loading / ready / error / unsupported各状态的含义并定位问题。一、什么是 OpenCode Free原生免凭证 ProviderOpenCode Free 是 Hermes Studio 内置的原生 provider对应标识opencode-free。它的核心特征是不需要任何凭证既不需要在 Studio 中配置 API Key也不需要手动创建 custom-provider 条目启动即出现服务启动后 provider 会直接出现在可用模型列表中包括那些已经存在其他 provider 目录缓存的安装环境目录公开且跨 profile 共享免费模型目录是公开数据不绑定某个 profile所有 profile 共用同一份目录模型可见性visibility与别名alias仍沿用 Studio 既有设置体系。从源码看这一设计在 packages/server/src/modules/studio/contracts/opencode-free.ts 中集中定义export const OPENCODE_FREE_PROVIDER opencode-free export const OPENCODE_FREE_BASE_URL https://opencode.ai/zen/v1也就是说该 provider 的模型列表来自公开端点https://opencode.ai/zen/v1完全匿名访问。而在模型目录缓存模块 packages/server/src/modules/hermes/services/providers/model-catalog-cache.ts 中opencode-free被特殊处理为跨 profile 的共享目录——resolveProviderCatalogEntry与writeProviderModelCatalogEntry在 provider 为opencode-free时强制忽略 profile 作用域profile 确保所有 profile 读到同一份免费目录。二、零副作用启动不触碰 Hermes 配置与默认模型OpenCode Free 的一个关键设计约束是启动过程不修改任何持久化配置。文档明确列出三条红线不修改 Hermes 的config.yaml不修改.env不修改默认模型。只有当用户显式将默认模型切换为某个免费模型时才会在配置中写入model.provider: opencode-free以及选中的模型 ID。聊天请求本身始终携带原生的 provider ID即opencode-free由 Hermes 侧自行决定传输协议与匿名认证行为——Studio 不做密钥注入。这一行为在测试 tests/server/opencode-free-models.test.ts 中有直接断言初始化开始后接口返回的default_provider仍为原有的deepseek、default仍为deepseek-chat且磁盘上的config.yaml内容与初始化前完全一致expect(readFileSync(...)).toBe(initialConfig)。三、后台初始化状态机与超时设计初始化在服务启动后后台异步执行绝不阻塞其他 provider 的加载。核心实现在 packages/server/src/modules/hermes/services/providers/opencode-free.ts整体是一个四态状态机export type OpenCodeFreeStatus loading | ready | error | unsupported状态含义触发条件loading正在初始化服务启动、尚无结果ready可用探测支持 目录拉取成功且非空error出错待重试目录拉取失败/超时/返回空unsupported运行时不支持探测显示 Hermes 无此 provider初始化流程由initializeOpenCodeFreeInBackground驱动使用Promise.allSettled并行执行两项任务const [support, catalog] await Promise.allSettled([probeSupport(), fetchOpenCodeFreeModels()]) if (catalog.status fulfilled catalog.value.length) { await writeProviderModelCatalogEntry({ provider: OPENCODE_FREE_PROVIDER, label: OpenCode Free, base_url: OPENCODE_FREE_BASE_URL, models: catalog.value, source: live, }) }3.1 运行时探测probeSupport探测环节用于确认当前选中的 Hermes Python 运行时是否在 provider 注册表中支持opencode-free。源码中嵌入了对 Hermes 的 Python 探测脚本from hermes_cli.auth import PROVIDER_REGISTRY; print(supported if opencode-free in PROVIDER_REGISTRY else unsupported)关键参数命令查找超时通过which/where.exe解析 Hermes 可执行文件路径时异步执行并带2 秒超时探测执行超时execFileAsync(python, [-c, PROBE], { timeout: 5000, ... })即5 秒超时探测的cwd为 Hermes 的agentRoot环境变量中注入HERMES_HOME。如果探测显示运行时不支持状态置为unsupported此时 provider 条目仍然保留在界面上并附带更新提示文案只是没有可选模型Studio 不会擅自自动升级 Hermes 运行时。3.2 目录拉取fetchOpenCodeFreeModels目录拉取实现在 packages/server/src/modules/studio/public/provider-catalog.tsexport async function fetchOpenCodeFreeModels(): Promisestring[] { return (await fetchProviderModels(OPENCODE_FREE_BASE_URL, )).filter(isOpenCodeFreeModel) }要点不携带Authorization头apiKey传空字符串fetchProviderModels仅在 apiKey 非空时才附加Bearer头请求超时 8 秒使用AbortSignal.timeout(8000)端点拼接规则若 base URL 以/v\d结尾则直接拼/models否则拼/v1/models即请求https://opencode.ai/zen/v1/models失败降级HTTP 非 2xx 或网络异常时记录日志并返回空数组不抛错中断主流程。3.3 模型过滤规则拉回原始模型 ID 列表后应用isOpenCodeFreeModel过滤见 contracts/opencode-free.tsexport function isOpenCodeFreeModel(model: string): boolean { return model.endsWith(-free) model ! ox-alpha-free }规则与 Hermes 的-free目录过滤器一致模型 ID 必须以-free结尾同时显式排除后缀同样以-free结尾、但仅面向 Go 运行时的ox-alpha-free模型避免把它误纳入可供 Studio 使用的列表。3.4 重试、刷新与去重初始化完成后的调度策略源码中的常量与逻辑参数值说明RETRY_MS60 000 ms1 分钟失败或空响应后的重试间隔REFRESH_MS300 000 ms5 分钟成功初始化后的周期刷新间隔失败保留 last-good拉取失败或返回空数组时不会用空结果覆盖已有的 Studio 目录缓存writeProviderModelCatalogEntry只在目录非空时才被调用单飞去重single-flightinflight标志确保同一时刻只有一个初始化任务在跑重复调用直接忽略定时器 unref重试定时器调用retryTimer.unref()保证定时器不会让服务器进程保持存活避免影响优雅退出。测试 tests/server/opencode-free.test.ts 对上述行为做了精确验证连续调用两次initializeOpenCodeFreeInBackground目录拉取只执行 1 次去重首次失败后第 59 999 ms 时仍不重试第 60 000 ms 时才触发第二次拉取1 分钟重试间隔目录返回空数组时不写缓存、状态为error探测返回unsupported时状态为unsupported。四、模型目录缓存启动即有、失败不覆盖OpenCode Free 的模型列表落地到 Studio 的提供商模型目录缓存cache/provider-model-catalog.json由 model-catalog-cache.ts 统一管理。缓存条目的核心字段如下interface ProviderModelCatalogEntry { provider: string label: string base_url: string models: string[] source: live | fallback updated_at: string free_only?: boolean profiles?: string[] // ... }初始化成功后写入的是一条source: live的条目例如测试中验证的写入参数{ provider: opencode-free, label: OpenCode Free, base_url: https://opencode.ai/zen/v1, models: [mimo-v2.5-free], source: live }缓存优先策略体现在两个场景已有缓存界面立即使用现有 Studio 目录缓存展示模型不等待网络无缓存显示一个loading占位条目而不是阻塞其他 provider 或凭空猜测模型 ID。同时refreshConfiguredProviderModelCatalogs在启动时若检测到缓存已存在则跳过整体刷新cache exists; skipping startup refresh确保免费 provider 的目录不会在每次启动时都强制触发网络请求。从测试还可以看到当缓存中混入非免费模型如paid-model时getAvailable接口返回给前端的模型列表会被过滤只保留-free结尾的模型见 tests/server/opencode-free-models.test.ts 中 filters out paid models 用例。五、前端行为后台轮询而非整页遮罩模型设置页与新建聊天new-chat抽屉在前端会在后台轮询provider 的初始化状态但有严格的边界约束仅在loading或重试阶段轮询页面卸载unmount时立即停止轮询不显示全页级 loading 遮罩不改变用户已选中的模型。也就是说用户打开模型列表时会先看到loading占位条目一旦后台初始化完成条目自动变为可选的免费模型列表全程无需刷新页面或等待阻塞。若运行时探测为unsupportedgetAvailable返回的 group 中models为空数组、catalog_status为unsupported前端据此渲染更新提示见 tests/server/opencode-free-models.test.ts 的 keeps the entry but disables models 用例。六、会话 API 路由Zen 分模式路由规则当聊天请求真正发出时Studio 依据模型 ID 解析运行参数。openCodeFreeRuntime(model)见 contracts/opencode-free.ts实现了 Hermes Zen 的分模式路由const apiMode /^(claude-|qwen)/.test(normalized) ? anthropic_messages : /^(gpt-|grok-|muse-spark)/.test(normalized) ? codex_responses : chat_completions return { baseUrl: OPENCODE_FREE_BASE_URL, apiKey: , apiMode }模型前缀API 模式说明claude-*、qwen*anthropic_messages走 Messages APIgpt-*、grok-*、muse-spark*codex_responses走 Responses API其余免费模型chat_completions走 Chat Completions API值得注意的是apiKey恒为空字符串认证完全依赖匿名机制若传入的模型并非-free结尾函数会直接抛出 HTTP 400 错误OpenCode Free requires a free model。新建聊天表单会自动选择对应 API 模式并且不询问 API Key。七、Scoped Coding Agents 免上游密钥支持opencode-free不仅作用于普通聊天还覆盖Scoped Coding AgentsClaude Code、Codex、Pi、Grok、OpenCode。在 packages/server/src/modules/coding-agents/services/index.ts 中当目标 provider 为opencode-free时运行参数直接由openCodeFreeRuntime(model)解析第 775-776 行并且多处校验逻辑都放行了无 API Key 的opencode-free如第 2068、2098、2199、3890 行的!apiKey provider ! OPENCODE_FREE_PROVIDER继续条件。具体行为生成的配置保留 Studio 本地代理 tokenCoding Agent 的配置文件仍然写入 Studio 的本地代理凭证用于把请求路由到本地代理代理侧净化代理会剥离上游过期的Authorization/x-api-key头并把上游地址固定为 OpenCode Zenhttps://opencode.ai/zen/v1Codex 与 Pi 的代理恢复逻辑已支持空上游凭据existing proxy restoration supports empty upstream credentials而其他 provider 仍然要求凭据Ekko 直接解析同一匿名运行时Ekko 与 Coding Agents 共用同一个openCodeFreeRuntime解析行为一致Global Coding Agent 模式仍使用 Agent 自身配置不受此机制影响。八、快速验证聚焦测试命令文档提供了一组聚焦检查命令用于验证 OpenCode Free 的初始化、模型过滤与界面呈现npx vitest run tests/server/opencode-free*.test.ts tests/server/provider-create-controller.test.ts tests/server/provider-model-refresh.test.ts npx playwright test tests/e2e/provider-models.spec.ts第一组 Vitest 用例覆盖后台初始化去重、超时重试、unsupported、空响应保护与模型目录的读写、过滤行为第二组 Playwright 用例在浏览器层面验证 provider 模型选择界面的端到端表现。运行前需在仓库根目录安装好依赖参见 README.md 与 DEVELOPMENT.md 中的环境准备说明。九、使用注意事项与边界最后需要强调的几点事实性约束免费远程模型目录中出现某个模型并不保证推理必然成功限流rate limits与上游服务不可用仍然可能发生不自动升级Hermes 运行时若缺少该 providerStudio 只提示更新、不会静默升级初始化异步首次启动且无缓存时模型列表会短暂处于loading属预期行为跨 profile 共享免费目录不区分 profile任何 profile 下都能看到同一份模型列表别名与可见性配置则沿用各 profile 既有设置。整体来看opencode-free是 Hermes Studio 中一个典型的零配置、后台化、强容错的集成模块从常量与运行时解析contracts/opencode-free.ts、后台初始化状态机opencode-free.ts、公开目录拉取provider-catalog.ts到缓存持久化与 Coding Agents 路由model-catalog-cache.ts、coding-agents/services/index.ts每一层都有明确的职责边界和对应的测试用例背书可作为理解该仓库原生 provider 集成范式的完整参考。【免费下载链接】hermes-studioEkko Studio is a local-first AI workspace for multi-agent chat, coding, and visual workflows, available on desktop and the web.项目地址: https://gitcode.com/gh_mirrors/he/hermes-studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
