dsh-opencode-go 0.1.24 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.en.md +59 -7
  2. package/README.md +164 -79
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/assets/icon.png +0 -0
  5. package/docs/advanced.en.md +15 -22
  6. package/docs/advanced.md +15 -22
  7. package/docs/assets/logo-dark.png +0 -0
  8. package/docs/assets/logo-light.png +0 -0
  9. package/docs/development.md +47 -119
  10. package/docs/independent-package.md +4 -2
  11. package/docs/usage-analytics.en.md +108 -0
  12. package/docs/usage-analytics.md +108 -0
  13. package/docs/verification.md +25 -928
  14. package/lib/client.js +3430 -2473
  15. package/lib/index.js +2650 -2440
  16. package/lib/types/accounts.d.ts +96 -31
  17. package/lib/types/adapter.d.ts +45 -48
  18. package/lib/types/analytics.d.ts +345 -0
  19. package/lib/types/catalog.d.ts +112 -6
  20. package/lib/types/client/AnalyticsDialog.d.ts +25 -0
  21. package/lib/types/client/Section.d.ts +51 -7
  22. package/lib/types/client/UsagePill.d.ts +6 -4
  23. package/lib/types/client/controller.d.ts +176 -0
  24. package/lib/types/client/index.d.ts +9 -12
  25. package/lib/types/client/{locales.d.ts → presentation.d.ts} +124 -0
  26. package/lib/types/config.d.ts +9 -14
  27. package/lib/types/contracts.d.ts +283 -0
  28. package/lib/types/conversion.d.ts +147 -0
  29. package/lib/types/index.d.ts +7 -39
  30. package/lib/types/sdk-types.d.ts +5 -54
  31. package/lib/types/transport.d.ts +63 -0
  32. package/lib/vendor-licenses.txt +36 -36
  33. package/package.json +76 -77
  34. package/docs/fork-review-2026-09-25.md +0 -191
  35. package/docs/native-call-comparison-v020-rc2.md +0 -116
  36. package/lib/types/account-manager.d.ts +0 -25
  37. package/lib/types/account-operations.d.ts +0 -31
  38. package/lib/types/account-policy.d.ts +0 -16
  39. package/lib/types/account-recovery.d.ts +0 -2
  40. package/lib/types/account-selection.d.ts +0 -30
  41. package/lib/types/account-service.d.ts +0 -12
  42. package/lib/types/accounts-contract.d.ts +0 -38
  43. package/lib/types/call-trace.d.ts +0 -54
  44. package/lib/types/catalog-manager.d.ts +0 -20
  45. package/lib/types/client/AccountsCard.d.ts +0 -19
  46. package/lib/types/client/ModelEditor.d.ts +0 -20
  47. package/lib/types/client/accounts-controller.d.ts +0 -71
  48. package/lib/types/client/hint-mark.d.ts +0 -17
  49. package/lib/types/client/host-compat.d.ts +0 -7
  50. package/lib/types/client/host-primitives.d.ts +0 -11
  51. package/lib/types/client/section-controller.d.ts +0 -202
  52. package/lib/types/client/settings.d.ts +0 -18
  53. package/lib/types/client/staged-form.d.ts +0 -201
  54. package/lib/types/client/usage.d.ts +0 -4
  55. package/lib/types/config-contract.d.ts +0 -70
  56. package/lib/types/conversion/config.d.ts +0 -4
  57. package/lib/types/conversion/context.d.ts +0 -56
  58. package/lib/types/conversion/image-offload.d.ts +0 -13
  59. package/lib/types/conversion/image-pool.d.ts +0 -24
  60. package/lib/types/conversion/index.d.ts +0 -5
  61. package/lib/types/conversion/replay.d.ts +0 -66
  62. package/lib/types/conversion/stream.d.ts +0 -44
  63. package/lib/types/gateway-diagnostics.d.ts +0 -23
  64. package/lib/types/gateway-error.d.ts +0 -2
  65. package/lib/types/gateway-url.d.ts +0 -6
  66. package/lib/types/google-fetch.d.ts +0 -6
  67. package/lib/types/host-settings.d.ts +0 -11
  68. package/lib/types/json-response.d.ts +0 -14
  69. package/lib/types/metadata-cache.d.ts +0 -12
  70. package/lib/types/model-metadata.d.ts +0 -21
  71. package/lib/types/models-contract.d.ts +0 -66
  72. package/lib/types/models.d.ts +0 -13
  73. package/lib/types/protocol-contract.d.ts +0 -5
  74. package/lib/types/protocol-policy.d.ts +0 -4
  75. package/lib/types/provider-identity.d.ts +0 -4
  76. package/lib/types/proxy-url.d.ts +0 -2
  77. package/lib/types/proxy.d.ts +0 -11
  78. package/lib/types/reasoning.d.ts +0 -40
  79. package/lib/types/remote-contract.d.ts +0 -3
  80. package/lib/types/remotes.d.ts +0 -3
  81. package/lib/types/request-budget.d.ts +0 -12
  82. package/lib/types/request-control.d.ts +0 -2
  83. package/lib/types/settings-bridge.d.ts +0 -22
  84. package/lib/types/usage-contract.d.ts +0 -35
  85. package/lib/types/usage-display.d.ts +0 -4
  86. package/lib/types/usage.d.ts +0 -27
  87. /package/lib/types/{google-generative-ai.d.ts → google.d.ts} +0 -0
@@ -1,116 +0,0 @@
1
- # 与 DeepSeek Harness v0.2.0-rc.2 原生调用的对照
2
-
3
- 研究日期:2026-09-30。插件:当前工作区 `dsh-opencode-go@0.1.17`,基线提交 `03c482369f400ae786a251a5df265309e5341bb1`。原生:固定 tag `dsh-v0.2.0-rc.2`,提交 `639ed015397290b3745d163aafe02ffee4aa3f84`。上游源码只读检出在系统临时目录;没有读取真实 API key,也没有发出收费推理请求。版本出处:[官方 release](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.0-rc.2)、[插件 package.json](../package.json)。
4
-
5
- 后续修复(2026-09-30):当前工作区已修复下文基线中的配置/credential 引用漂移与 Anthropic alias 回放缺陷,并重建 `lib/`。请求在目录等待前复制配置,准备与发送使用同一份模型/目录/密钥引用;回放以请求模型 ID 匹配签名,返回别名仅作为说明信息保留。371 项测试、32 个产物检查、九个宿主版本兼容检查及原始请求对照均通过,见[修复验证](verification.md#prepared-call-configuration-and-anthropic-alias-replay-2026-09-30)。下文保留修复前的对照证据;默认思考和输出上限等策略差异仍适用。
6
-
7
- ## 结论
8
-
9
- 插件与 rc.2 的原生 OpenCode Go 使用同一版 pi-ai 0.87.1,常规文本、工具、流式与用量转换大体一致,但不是完全等价的调用。原生现在也会自动添加会话路由 header;这已不再是插件独有能力。插件的主要额外价值是在线模型目录、独立设置与额度界面,以及针对部分模型自动启用思考的默认策略。
10
-
11
- 本地 HTTP mock 已复现两个应优先修复的问题:**插件没有完整冻结一次调用的配置,准备后改 endpoint 会从 A 改发 B,目录刷新期间改 endpoint 与 key 引用还会组合旧 endpoint A 和新 fixture key B;Anthropic 响应 model 与请求 alias 不同时,原生下一轮保留 thinking 签名,插件把 thinking 降为普通文本并丢失签名。** 另有两项明确策略差异:未选思考档位时插件为部分模型默认启用 high;配置输出上限 128、显式请求 512 时,原生发送 512,插件发送 128。统一 effort、maxTokens 与 temperature 后,DeepSeek V4 Flash、Qwen3.6-plus、GLM5.3 的完整请求 JSON、会话 header、User-Agent 均一致。详见后文证据。
12
-
13
- 这里有两种“原生”:
14
-
15
- | 对照对象 | 调用路径 | 是否与插件使用同一网关 |
16
- | --- | --- | --- |
17
- | 原生 OpenCode Go | `@deepseek-ai/dsh-llm-pi-ai` → pi-ai `opencode-go` provider → OpenCode Go | 是;这是主要对照对象 |
18
- | 原生 DeepSeek 官方直连 | `@deepseek-ai/dsh-llm-deepseek-api-key` / account → `dsh-llm-deepseek` → DeepSeek Messages API | 否;协议、认证和能力均有差异 |
19
-
20
- 原生 pi-ai 会复用安装包内置 provider,插件则构建私有的 `opencode-go` SDK provider,并以 `dsh-opencode-go` 注册到 DSH。官方直连默认端点为 `https://api.deepseek.com/anthropic`。来源:[原生 provider 构建](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/provider.ts#L144-L172)、[插件 provider 构建](../src/catalog.ts#L125)、[插件身份](../src/provider-identity.ts)、[官方直连配置](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-deepseek/src/config.ts#L106)。
21
-
22
- ## 原生 OpenCode Go 与插件
23
-
24
- ### 请求协议、会话 header 与认证
25
-
26
- | 项目 | 插件 | rc.2 原生 OpenCode Go | 影响 |
27
- | --- | --- | --- | --- |
28
- | SDK | 私有 npm alias `opencode-go-pi-ai` → pi-ai 0.87.1 | `@earendil-works/pi-ai` 0.87.1 | 同版基线;原生源码构建另有流式性能补丁,见后文 |
29
- | DSH provider ID | `dsh-opencode-go` | `opencode-go` | 可以共存,DSH 持久历史来源名称不同 |
30
- | 带 sessionId | 显式发 `x-opencode-session: sessionId` | 内置 `withOpenCodeSessionHeader` 自动补同一值 | 常规 DSH 会话一致 |
31
- | 无 sessionId | 每个请求生成随机 UUID header | 不自动补 header | 插件为无会话调用提供隔离的路由值 |
32
- | User-Agent | `attributionHeaders()` | `attributionHeaders()`,覆盖 profile 中同名 header | 都归因到当前 Harness |
33
- | API key | 每次通过 DSH credentials / launch environment 解析 `OPENCODE_API_KEY` 引用 | 可通过 profile 显式引用,也可使用 pi-ai 原生认证、存储与登录机制 | OpenCode Go 都能用同一环境引用;原生通用 adapter 认证范围更广 |
34
- | 自定义 HTTP header | 没有对应配置项 | profile `headers` | 原生部署可配置附加 header |
35
-
36
- 来源:[插件依赖](../package.json)、[原生依赖](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/package.json)、[插件请求选项](../src/adapter.ts#L317)、[插件 credential resolver](../src/index.ts#L92)、[原生 header 合并](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/adapter.ts#L204-L211)、[原生 auth](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/auth.ts)。SDK 原始代码:[本地 pi-ai 0.87.1 provider](../node_modules/opencode-go-pi-ai/dist/providers/opencode-go.js)、[会话 header wrapper](../node_modules/opencode-go-pi-ai/dist/providers/opencode-headers.js)。
37
-
38
- 插件 [src/index.ts](../src/index.ts#L7) 仍称 generic pi-ai 不能表达该路由 header,这段说明对 rc.2 的内置 `opencode-go` 已过时。只有手工定义通用协议 provider、没有复用内置 OpenCode provider 时,不能直接套用上述自动 header 结论。
39
-
40
- ### 模型目录与思考默认值
41
-
42
- 原生使用 pi-ai 随安装包发布的模型目录;显式 `models` 替换目录,`modelOverrides` 修正单个条目。原生对已知 provider 的“发现模型”直接返回内置目录,不查询网关。插件则以网关 `${baseURL}/models` 决定可用 ID,以 `https://models.dev/api.json` 的 `opencode-go` 条目决定名称、协议、容量、模态和价格;内置目录作为兼容提示,已知历史数据用于故障时保留。插件还有磁盘元数据缓存、ETag、刷新与可见性开关。首次网关 listing 失败且没有已服务快照时,不会仅靠内置目录猜测可用模型。来源:[原生配置](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/config.ts#L105-L127)、[原生 discovery](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/discovery.ts#L269-L285)、[插件目录](../src/catalog.ts#L162)、[插件 membership 与故障策略](../src/catalog.ts#L263)、[插件元数据转换](../src/model-metadata.ts#L73)。
43
-
44
- 插件接受线上目录中的新 ID,前提是能解析为三种支持协议之一,且容量、模态、思考声明可用;缺少元数据的模型会显示诊断,不进入可选模型列表。这也意味着同一 pi-ai 版本并不保证同一模型描述:线上更新可以让插件与原生在容量、协议或思考选项上发生差别。插件只读取 `opencode-go` 元数据,忽略在线端点、header 与 credential,实际模型流量仍使用配置的网关。来源:[插件目录构建](../src/catalog.ts#L285)、[metadata 读取规则](../src/model-metadata.ts#L68)、[picker 过滤](../src/adapter.ts#L166)。
45
-
46
- 默认思考是明确的策略差异。原生只在 profile 指定 `reasoning` 时给模型提供 `defaultEffort`;插件遇到 `deepseek`、`zai`、`qwen`、`qwen-chat-template` 这类未指定 effort 会显式关闭思考的格式,会提供 `high`,或模型支持的最高非 off 档。DSH 正常调用会把该默认值补入请求。本地 mock 确认:DeepSeek V4 Flash 原生发 `thinking: disabled`,插件发 `thinking: enabled` 与 high;Qwen3.6-plus 在 fixture 声明 toggle 时原生发 `enable_thinking: false`,插件发 true 与 high;GLM5.3 两边都未默认指定 effort。该结果验证默认策略,不能视为真实服务推理质量对比。若直接调用 adapter.stream 而绕过 DSH 的默认值补齐,两边默认行为还会有所不同。来源:[插件默认 effort](../src/adapter.ts#L198)、[原生 reasoningInfo](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/adapter.ts#L187-L201)、[DSH 默认值补齐](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm/src/index.ts#L892-L924)、[pi-ai thinking 序列化](../node_modules/opencode-go-pi-ai/dist/api/openai-completions.js#L619)、本文验证表。
47
-
48
- ### 输出 cap 与调用配置冻结
49
-
50
- 原生将显式配置的 model `maxTokens` 暴露为 `defaultMaxTokens`:只有请求没有 cap 时 DSH 才补默认值,调用方显式值优先。插件不暴露 `defaultMaxTokens`,而在发送时对 modelLimits 的 `maxTokens` 做 `Math.min(requested, configured)`,因此它是强制上限。本地 mock 设配置 128、请求 512,原生实际发送 512,插件实际发送 128;原生模型信息返回 defaultMaxTokens=128,插件没有该字段。这属于可见语义差异,且 DSH 准备的调用配置可能没有反映插件最终上限。两边都不会仅因为内置目录有 `maxTokens` 就在 adapter 层自动填入整个容量;SDK 在无显式 cap 时仍可能自行采用模型容量。本次默认请求中原生与插件的容量数值来自不同 fixture 目录,不能用那组数值认定额外缺陷。来源:[原生 modelInfo](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/adapter.ts#L300-L315)、[DSH defaultMaxTokens](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm/src/index.ts#L896-L898)、[插件 cap](../src/adapter.ts#L266)、[插件 modelInfo](../src/adapter.ts#L223)、本文验证表。
51
-
52
- rc.2 原生 `PiAiAdapter.prepareCall` 绑定不可变 profile/models 快照;`DeepSeekAdapter.prepareCall` 也绑定 connection。插件继承 base `LlmAdapter.prepareCall`,仅先 resolveModel,随后调用普通 stream 再读配置。本地 mock 已验证:在 endpoint A prepareCall 后将配置改到 B,原生发送到 A,插件发送到 B。这违背 rc.2 动态 adapter 应把能力解析与发送绑定同一配置代的要求;普通 stream 入口开始后捕获一次 config,不能覆盖准备到发送的时间段。来源:[rc.2 base prepareCall 要求](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm/src/index.ts#L269-L282)、[原生 pi-ai prepareCall](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/adapter.ts#L318-L347)、[原生 DeepSeek prepareCall](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-deepseek/src/adapter.ts#L43-L48)、[插件 stream](../src/adapter.ts#L247)、本文验证表。
53
-
54
- 另一个独立窗口是 credential 引用。插件 capture config 后先 await catalog,再调用零参数 resolveApiKey;该 resolver 重新读取 `current().apiKeyEnv`。本地 mock 使用真实 plugin.apply、live config 和两个固定假 key:阻塞 models.dev 元数据请求,期间将 endpoint A→B、key 引用 A→B,再释放目录请求,实际捕获到 **endpoint A 与 fixture key B**。这证明旧 endpoint 与新 credential 引用会组合,修复应把 credential 引用也绑定到同一次调用配置。原生 pi-ai 将已捕获的 profile 传给 credential resolver,官方直连将 connection 传给 resolveAuth。没有读取或使用真实 credential。来源:[插件等待与 credential](../src/adapter.ts#L251)、[插件 resolver](../src/index.ts#L92)、[原生 resolver](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/index.ts#L187-L205)、本文验证表。
55
-
56
- ### 历史、回放与流式
57
-
58
- 插件 context 基于原生 pi-ai 转换:相同的单一 systemPrompt 分离、assistant text/reasoning/tool-call、工具名恢复与图像引用转换。插件保留旧 DSH user 内嵌 `tool-result` 的兼容处理;rc.2 原生使用独立 `tool` role。两边都拒绝 developer 历史、tool-addition/tool-removal、deferred tool loading、assistant/system 图片以及 `GenerateOptions.stop`。来源:[插件 context](../src/conversion/context.ts#L51)、[旧 tool result 兼容](../src/conversion/context.ts#L232)、[原生 context](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/context.ts#L49-L64)、[原生 tools](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/context.ts#L129-L141)、[原生 stop](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/adapter.ts#L334-L336)。
59
-
60
- 两边都用 `kind: pi-ai, version: 2` 回放,保存原生 responseId、thinking/text/tool 签名、providerThinkingLevel;不再可用的回放元数据会降级为 provider-neutral 内容并告警。插件额外保存 `sdkProvider`:持久来源为 `dsh-opencode-go`,SDK 同模型判断时恢复 `opencode-go`,解决私有 DSH 路由与 SDK provider 的身份差别。来源:[插件 replay](../src/conversion/replay.ts#L82)、[插件恢复](../src/conversion/replay.ts#L196)、[原生 replay](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/replay.ts#L79)。
61
-
62
- **Anthropic model 别名导致思考签名回放丢失,已用 mock 复现。** 插件将 Anthropic 响应的 native `message.model` 存为 responseModel,回放时以 responseModel 恢复 assistant.model;原生始终以请求 model 恢复。pi-ai `transformMessages` 的同模型判断要求 provider、api 与 model.id 三者相同,跨模型时会将普通 thinking 转 text、去掉签名。本地 mock 请求 minimax-m3、响应 minimax-m3-returned-alias,将回放 envelope 经 JSON 往返后续聊同一请求 ID:原生发送 thinking 与 fixture-signature,插件发送包含相同思考文本的普通 text,签名消失。该验证证明明确条件下的回放缺陷,未证明真实 OpenCode 网关当前是否返回不同 alias。来源:[插件 responseModel 与恢复](../src/conversion/replay.ts#L82)、[插件恢复 model](../src/conversion/replay.ts#L231)、[原生恢复 model](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/replay.ts#L218-L226)、[SDK 同模型判断](../node_modules/opencode-go-pi-ai/dist/api/transform-messages.js#L65)、本文验证表。
63
-
64
- 流式与用量映射的代码对照显示:除传入 requestedProvider 的回放差别,两边相同地发 text/reasoning/tool 参数增量、block-end、usage、finish;相同地处理空输出、上下文超限、401/403、quota/rate-limit、413、服务器错误、超时、截断和 abort。插件与原生都按 pi-ai 原值映射 input/output/total/cacheRead/cacheWrite,reasoning token 由 pi-ai 计入 output;不能把 cacheRead 字段等同于一定命中网关缓存。来源:[插件 stream](../src/conversion/stream.ts)、[原生 stream](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/stream.ts)。
65
-
66
- 同版 SDK 仍有构建差异:上游源码维护 pi-ai 0.87.1 patch,删除工具参数每个 delta 对累计 JSON 的重复解析,保留完成时处理;插件私有 npm alias 不带该 patch。源码/应用构建采用此补丁时,长工具参数流式有额外性能优化;本次 npm 临时 host 不能自动代表补丁已应用,也没有进行性能基准测试。该项不能定性为正确性差异。来源:[上游 pi-ai patch](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/patches/%40earendil-works__pi-ai%400.87.1.patch)、[插件依赖](../package.json)。
67
-
68
- ### 图片、超时、重试与额度
69
-
70
- 两边均只允许模型声明 image modality 且挂载 durable attachment service 时发送图片,以 base64 内联,每张默认 2048×2048 像素预算、1 MiB 原始编码目标,整个请求 20 MiB base64 上限。在 rc.2 上,超预算都会发 `IMAGE_OFFLOAD_REQUIRED`,由宿主记录卸载后重试,已卸载图片转文字占位。插件额外可配置 `maxImages`,并保留 0.1.5 旧宿主临时投影逻辑;这不是 rc.2 原生图像方案缺失。来源:[插件预算](../src/conversion/config.ts)、[插件 offload bridge](../src/conversion/image-offload.ts)、[原生预算](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/config.ts#L46-L62)、[原生图像处理](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/context.ts#L294-L338)。
71
-
72
- 两边 stream idle timeout 默认均为 300,000 ms,并把 SDK `maxRetries` 设为 0,由 Harness agent recovery 拥有可见重试。原生额外配置 `timeoutMs`、`websocketConnectTimeoutMs`、`transport`、`cacheRetention`、`thinkingBudgets` 和 provider `retryPolicy`;插件只暴露 streamIdleTimeoutMs 等专用选项,没有覆写 providerRetryPolicy,使用 DSH base 的正常默认策略。因此 SDK retry=0 不意味着整个 agent 不重试。来源:[原生配置选项](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/config.ts#L153-L182)、[原生 stream options](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-pi-ai/src/adapter.ts#L114-L131)、[插件选项](../src/adapter.ts#L317)、[base retryPolicy](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm/src/index.ts#L218-L224)。
73
-
74
- 插件额度服务另外读取 `${baseURL}/usage`,在 Host 发送 Bearer key,展示账户额度;这是账户统计,与每次推理的 token usage 分开。原生 llm-pi-ai 没有这个专用 GoUsageService。来源:[插件 usage](../src/usage.ts#L24)、[usage UI](../src/client/UsagePill.tsx)。
75
-
76
- ## 与 DeepSeek 官方直连的补充对照
77
-
78
- rc.2 `dsh-llm-deepseek` 是独立 Messages transport,API key 官方路由由 `dsh-llm-deepseek-api-key` 挂载为 `deepseek-official`,使用 `x-api-key`;另外有 account 认证入口。它直接 fetch `/messages`,发送 `anthropic-version`、匿名 Harness user ID、Harness session ID 与 compaction header,支持官方 API extensions。插件通过 OpenCode Go 调用 DeepSeek 模型时走 OpenAI Completions 兼容协议,因此请求并不相同。来源:[官方 route 与 auth](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-deepseek-api-key/src/index.ts#L15-L41)、[Messages fetch](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-deepseek/src/adapter.ts#L103-L147)。
79
-
80
- | 能力 | 插件 / 原生 pi-ai | 官方 Messages 直连 |
81
- | --- | --- | --- |
82
- | 思考默认 | 插件针对指定格式补 high;原生由 profile 控制 | 默认 high,支持 off / low / high / max;session-title 强制 off |
83
- | 默认输出 cap | 取决于显式配置,不自动使用全目录容量 | 连接默认 256,000,模型项和显式请求值优先;对 DSH 暴露 defaultMaxTokens |
84
- | 中途 system/developer 更新 | context 转换拒绝 developer / tool-change | 按模型声明处理 system in-history 与 developer 更新 |
85
- | 延迟工具加载与增删 | 拒绝 deferred loading | 序列化 defer_loading、tool_addition、tool_removal |
86
- | stop | adapter 拒绝 | 序列化 stop_sequences |
87
- | 图片上传与复用 | base64 内联 | 优先 Files API,保存/复用 file_id;文件解析失败可退回 base64 |
88
- | 图像 token 估算 | 未声明 provider 专用 pricing | DeepSeek token-grid pricing 与模型像素策略 |
89
- | 原生回放 | pi-ai v2,多 provider 格式 | deepseek-messages v1,thinking signature |
90
-
91
- 来源:[官方 serialize](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-deepseek/src/serialize.ts#L56-L167)、[官方 modelInfo](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-deepseek/src/model-info.ts#L61-L99)、[官方图片](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-deepseek/src/images.ts#L38-L110)、[file fallback](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-deepseek/src/adapter.ts#L84-L105)、[官方 image pricing](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-deepseek/src/request-pricing.ts)、[官方 replay](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-deepseek/src/replay.ts#L29-L64)。
92
-
93
- 官方 Messages 流式解析独立校验块、JSON 与 SSE 结束事件;其 totalTokens 在 translator 中按输入、输出、cache read/write 相加。插件/原生 pi-ai 使用 SDK 提供的 usage.totalTokens。不同协议可能把 cache 字段包含在哪个输入字段中的语义不同,不能直接凭字段名推断账单差异;需要同一服务实际 usage 证据。来源:[官方 translator](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/llm-deepseek/src/translate.ts#L143-L165)、[插件 mapUsage](../src/conversion/stream.ts#L24)。
94
-
95
- ## 验证与建议
96
-
97
- 已完成固定 tag 源码和本地 npm 0.87.1 实现的只读对照。`npm run test:compat -- v020-rc2` 全部通过,`npm run check:dist` 32 个产物通过;另在临时目录安装真实 rc.2 npm 包与插件 tarball,启动两个 loopback HTTP mock,以假 key 和固定 SSE 响应运行对照,实际捕获 URL、body、会话 header 与 User-Agent。模型目录和 models.dev 响应由 fixture 提供,probe 阻止其他网络推理请求。所有断言通过,结果如下。
98
-
99
- | 验证场景 | 原生结果 | 插件结果 |
100
- | --- | --- | --- |
101
- | DeepSeek V4 Flash 不选 effort | thinking disabled | thinking enabled / high |
102
- | Qwen3.6-plus,fixture 声明 toggle | enable_thinking=false | enable_thinking=true / high |
103
- | GLM5.3 不选 effort | 无默认 effort | 无默认 effort |
104
- | 上述三模型统一显式 effort / maxTokens=512 / temperature=0.2 | 完整 body、session header、User-Agent | 与原生 deepEqual |
105
- | 指定 comparison-session | x-opencode-session 同值、Harness User-Agent | 两项完全一致 |
106
- | 不指定 sessionId | 无会话 header | 随机 UUID 会话 header |
107
- | 配置 cap=128,请求 cap=512 | 模型 defaultMaxTokens=128,实际 512 | 无 defaultMaxTokens,实际 128 |
108
- | prepareCall 后 A→B | 实际请求 A | 实际请求 B |
109
- | 插件目录请求期间改 endpoint/key 引用 | 原生源码绑定已捕获 profile | 实际组合旧 endpoint A 与 fixture key B |
110
- | Anthropic model alias,经 JSON 往返后续聊 | thinking 与签名保留 | thinking 变 text,签名丢失 |
111
-
112
- 临时验证证据:[results.json](/tmp/dsh-native-call-probe.b3lfUG/results.json)、[probe.mjs](/tmp/dsh-native-call-probe.b3lfUG/probe.mjs)。这些是本次运行的临时诊断材料,没有加入永久测试;临时目录清理后链接可能失效。没有真实网关推理质量、缓存命中率、延迟或计费对比结果。
113
-
114
- 验证准备过程中,首轮临时 probe 未设置隔离的 DSH_HOME,曾将默认公共 models.dev 元数据缓存写为仅含 4 个模型的 fixture。随后脚本改为按 PID 隔离临时目录,并从官方 `https://models.dev/api.json` 成功恢复公共缓存,校验源 URL、33 个 OpenCode Go 模型、ETag 存在与正确缓存格式。用户配置和凭据未修改。
115
-
116
- 建议优先修复调用配置及 credential 引用冻结与 Anthropic alias replay;随后决定 modelLimits.maxTokens 是强制 cap 还是默认值,并确保 DSH 记录与实际发送一致。文档应更新原生 OpenCode Go 已支持 x-opencode-session 的事实。是否继续使用插件主要取决于是否需要动态目录和额度/专用设置;仅为添加会话 header,rc.2 已无需单独插件。
@@ -1,25 +0,0 @@
1
- import { type CredentialProvider } from '@deepseek-ai/dsh-credentials';
2
- import { type AccountSettingsWriter, type SettingsWriteResult } from './settings-bridge.ts';
3
- import { type AccountCommand } from './accounts-contract.ts';
4
- export declare const ACCOUNT_RECOVERY_TIMEOUT_MS = 30000;
5
- /** Serialize account commands and recover the same durable operations for one profile. */
6
- export declare class GoAccountManager {
7
- private readonly logger;
8
- private connection;
9
- private pending;
10
- private again;
11
- private queue;
12
- constructor(logger: {
13
- warn(message: string): void;
14
- });
15
- connect(settings: AccountSettingsWriter, credentials: Pick<CredentialProvider, 'describe' | 'unset'> & Partial<Pick<CredentialProvider, 'set'>>): () => void;
16
- trigger(): void;
17
- recover(): Promise<void>;
18
- private row;
19
- private run;
20
- /** Preserve an old selection by moving it first in the same revision-guarded write. */
21
- private normalizeOrder;
22
- private complete;
23
- private enqueue;
24
- execute(raw: AccountCommand): Promise<SettingsWriteResult>;
25
- }
@@ -1,31 +0,0 @@
1
- /** Durable, client-safe intentions. Never put a credential value in this document. */
2
- import { type AccountSettings, type GoAccount } from './accounts.ts';
3
- export type GoAccountOperation = {
4
- id: string;
5
- kind: 'add';
6
- account: GoAccount;
7
- previousRef: string;
8
- select: boolean;
9
- } | {
10
- id: string;
11
- kind: 'remove';
12
- account: GoAccount;
13
- };
14
- export declare function ownsAccountCredential(account: GoAccount): boolean;
15
- export declare function assertAccountOperations(value: readonly GoAccountOperation[] | undefined): void;
16
- /** Pending additions remain visible so a lost key write can be repaired or removed. */
17
- export declare function visibleAccountsOf(settings: AccountSettings): readonly GoAccount[];
18
- /** The same transition completes a foreground operation or a recovered intention. */
19
- export declare function accountOperationPatch(settings: AccountSettings, operation: GoAccountOperation, dropLegacyRef?: string): ({
20
- op: "set";
21
- path: string[];
22
- value: GoAccount[];
23
- } | {
24
- op: "set";
25
- path: string[];
26
- value: string;
27
- } | {
28
- op: "set";
29
- path: string[];
30
- value: GoAccountOperation[];
31
- })[];
@@ -1,16 +0,0 @@
1
- import type { GoAccountSwitch } from './accounts.ts';
2
- export interface AttemptOutcome {
3
- failure?: {
4
- code: string;
5
- };
6
- emitted: boolean;
7
- totalTokens: number;
8
- aborted: boolean;
9
- hasNext: boolean;
10
- }
11
- /** Switching accounts must never replay output or already charged work. */
12
- export declare function accountFallback(outcome: AttemptOutcome): GoAccountSwitch['reason'] | undefined;
13
- /** Missing local credentials and exhausted quota do not blacklist a gateway key. */
14
- export declare function isGatewayKeyRejection(failure: {
15
- code: string;
16
- }): boolean;
@@ -1,2 +0,0 @@
1
- /** Backward-compatible name for the host's shared foreground/recovery executor. */
2
- export { GoAccountManager as GoAccountRecovery, ACCOUNT_RECOVERY_TIMEOUT_MS } from './account-manager.ts';
@@ -1,30 +0,0 @@
1
- import type { GoAccountSettlement } from './adapter.ts';
2
- import { type GoAccountSwitch } from './accounts.ts';
3
- import type { OpencodeGoConfig } from './config.ts';
4
- import { type AccountSettingsWriter } from './settings-bridge.ts';
5
- export type { AccountSettingsWriter } from './settings-bridge.ts';
6
- /** Owns both the fallback notice and its optional persisted account selection. */
7
- export declare class GoAccountSelection {
8
- private readonly config;
9
- private readonly logger;
10
- private settings;
11
- private connection;
12
- private credentials;
13
- private version;
14
- private signature;
15
- private latestSeq;
16
- private notice;
17
- constructor(config: () => OpencodeGoConfig, logger: {
18
- debug(message: string): void;
19
- warn(message: string): void;
20
- });
21
- connect(settings: AccountSettingsWriter): () => void;
22
- private row;
23
- /** Reading the revision also catches A → C → A before async change watchers run. */
24
- capture(): number;
25
- credentialChanged(): void;
26
- lastSwitch(): GoAccountSwitch | undefined;
27
- /** One freshness decision governs both side effects of a request's outcome. */
28
- settle(event: GoAccountSettlement): void;
29
- private adopt;
30
- }
@@ -1,12 +0,0 @@
1
- import type { Context } from '@deepseek-ai/cordis';
2
- import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
3
- import type { AccountCommand } from './accounts-contract.ts';
4
- import type { GoAccountManager } from './account-manager.ts';
5
- /** The browser sends commands; the host owns credentials, revisions and recovery. */
6
- export declare class GoAccountsService extends TypertRemoteService {
7
- private readonly options;
8
- constructor(ctx: Context, options: {
9
- manager: GoAccountManager;
10
- });
11
- execute(command: AccountCommand): Promise<import("./settings-bridge.ts").SettingsWriteResult>;
12
- }
@@ -1,38 +0,0 @@
1
- import type { RemoteResult, TypertRemoteContribution } from '@deepseek-ai/dsh-typert-protocol';
2
- import type { SettingsWriteResult } from './settings-bridge.ts';
3
- export type AccountCommand = {
4
- kind: 'add';
5
- id: string;
6
- name: string;
7
- key: string;
8
- } | {
9
- kind: 'remove';
10
- ref: string;
11
- } | {
12
- kind: 'select';
13
- ref: string;
14
- } | {
15
- kind: 'rename';
16
- ref: string;
17
- name: string;
18
- } | {
19
- kind: 'replace-key';
20
- ref: string;
21
- key: string;
22
- } | {
23
- kind: 'move';
24
- ref: string;
25
- toIndex: number;
26
- } | {
27
- kind: 'auto-switch';
28
- value: boolean;
29
- };
30
- export declare function parseAccountCommand(value: unknown): AccountCommand;
31
- declare module '@deepseek-ai/dsh-typert-protocol' {
32
- interface TypertRemoteNamespaceMap {
33
- opencodeGoAccounts: {
34
- execute(command: AccountCommand): Promise<RemoteResult<SettingsWriteResult>>;
35
- };
36
- }
37
- }
38
- export declare const accountsRemote: TypertRemoteContribution;
@@ -1,54 +0,0 @@
1
- import type { ImagePoolObservation } from './conversion/image-pool.ts';
2
- type Outcome = 'completed' | 'failed' | 'aborted' | 'consumer-stopped';
3
- type Stages = Partial<Record<'discovery' | 'credential' | 'images' | 'stream', number>>;
4
- export interface GoAttemptTrace {
5
- attempt: number;
6
- elapsedMs: number;
7
- stages: Stages;
8
- outcome: Outcome;
9
- code?: string;
10
- httpStatus?: number;
11
- requestId?: string;
12
- /** First text, reasoning or tool argument delta, measured from attempt start. */
13
- firstOutputMs?: number;
14
- imagePool?: {
15
- queueWaitMs: number;
16
- maxActive: number;
17
- maxQueued: number;
18
- maxRetainedBytes: number;
19
- rejected: boolean;
20
- };
21
- }
22
- /** Safe per-call diagnostics: no transcript, credential, URL or raw session id. */
23
- export interface GoCallTrace {
24
- callId: string;
25
- model: string;
26
- elapsedMs: number;
27
- stages: Stages;
28
- attempts: number;
29
- attemptDetails: readonly GoAttemptTrace[];
30
- outcome: Outcome;
31
- code?: string;
32
- }
33
- export declare class CallTrace {
34
- private readonly model;
35
- private readonly observer?;
36
- private readonly started;
37
- readonly callId: `${string}-${string}-${string}-${string}-${string}`;
38
- readonly stages: GoCallTrace['stages'];
39
- attempts: number;
40
- outcome: GoCallTrace['outcome'];
41
- code: string | undefined;
42
- private readonly details;
43
- private current;
44
- private finished;
45
- constructor(model: string, observer?: ((trace: GoCallTrace) => void) | undefined);
46
- startAttempt(): void;
47
- response(status: number, requestId?: string): void;
48
- firstOutput(): void;
49
- imagePool: (value: ImagePoolObservation) => void;
50
- endAttempt(outcome?: Outcome, code?: string): void;
51
- measure<T>(stage: keyof GoCallTrace['stages'], work: () => Promise<T>): Promise<T>;
52
- finish(): void;
53
- }
54
- export {};
@@ -1,20 +0,0 @@
1
- import { OpencodeGoCatalog } from './catalog.ts';
2
- import type { OpencodeGoConfig } from './config-contract.ts';
3
- import type { ProxyTransport } from './proxy.ts';
4
- /** One mount owns discovery; inference, settings and pickers share its snapshots. */
5
- export declare class GoCatalogManager {
6
- private readonly options;
7
- private current;
8
- constructor(options: {
9
- transport: ProxyTransport;
10
- onFallback?: (detail: {
11
- url: string;
12
- error: unknown;
13
- kept: number;
14
- }) => void;
15
- onOmitted?: (ids: readonly string[]) => void;
16
- onRefresh?: () => void;
17
- onWarning?: (warning: string) => void;
18
- });
19
- forConfig(config: OpencodeGoConfig): OpencodeGoCatalog;
20
- }
@@ -1,19 +0,0 @@
1
- import type { GoAccountsActions, GoAccountsState } from './accounts-controller.ts';
2
- import type { en } from './locales.ts';
3
- type Translate = (key: keyof typeof en, params?: Record<string, unknown>) => string;
4
- /**
5
- * The account card: a folded header that summarizes the set, and one row per
6
- * account inside. Rows keep the identity, key state, rolling quota and its reset
7
- * countdown; everything else opens under the row. The handle is the drag source —
8
- * dragging it, or the arrow keys on it, reorders the list, and the first row is
9
- * the preferred account. Every row is read in the same pass, so the one refresh
10
- * stamp belongs to the card and sits in its header.
11
- */
12
- export declare function AccountsCard({ state, actions, writable, t, locale }: {
13
- state: GoAccountsState;
14
- actions: GoAccountsActions;
15
- writable: boolean;
16
- t: Translate;
17
- locale?: string;
18
- }): import("react").JSX.Element;
19
- export {};
@@ -1,20 +0,0 @@
1
- import { type ReactNode } from 'react';
2
- import type { OpencodeGoModelLimit, OpencodeGoModelLimits, OpencodeGoModels } from './section-controller.ts';
3
- import type { en } from './locales.ts';
4
- type Translate = (key: keyof typeof en, params?: Record<string, unknown>) => string;
5
- export declare function hasCapacityOverride(limit: OpencodeGoModelLimit | null | undefined): boolean;
6
- /** Per-model switches apply immediately; capacity edits stay in the staged form. */
7
- export declare function ModelEditor({ models, draft, modelVisibility, t, locale, disabled, visibilitySaving, filterAction, onEdit, onModelEnabled }: {
8
- models: OpencodeGoModels;
9
- draft: OpencodeGoModelLimits;
10
- modelVisibility: Readonly<Record<string, boolean>>;
11
- t: Translate;
12
- locale?: string;
13
- disabled: boolean;
14
- visibilitySaving: boolean;
15
- /** Optional control pinned to the right of the filter row, such as a refresh. */
16
- filterAction?: ReactNode;
17
- onEdit: (next: OpencodeGoModelLimits) => void;
18
- onModelEnabled: (id: string, enabled: boolean) => void;
19
- }): import("react").JSX.Element;
20
- export {};
@@ -1,71 +0,0 @@
1
- import type { Context } from '@deepseek-ai/cordis';
2
- import { type AccountSettings, type GoAccount } from '../accounts.ts';
3
- import type { GoUsage } from '../usage-contract.ts';
4
- import type { SettingsScope } from './settings.ts';
5
- import type { AccountCommand } from '../accounts-contract.ts';
6
- import type { SettingsWriteResult } from '../settings-bridge.ts';
7
- export type ExecuteAccountCommand = (command: AccountCommand) => Promise<SettingsWriteResult>;
8
- export interface GoAccountView extends GoAccount {
9
- configured?: boolean;
10
- writable?: boolean;
11
- usage?: GoUsage;
12
- updatedAt?: number;
13
- stale?: boolean;
14
- loading?: boolean;
15
- failed?: boolean;
16
- /** Why the last quota read failed, safe to show: it carries no credential value. */
17
- problem?: string;
18
- }
19
- export interface GoAccountsState {
20
- entries: readonly GoAccountView[];
21
- activeRef: string;
22
- autoSwitch: boolean;
23
- busy: boolean;
24
- blocked: boolean;
25
- refreshing: boolean;
26
- failure?: 'write' | 'cleanup' | 'remove' | 'read';
27
- }
28
- export interface GoAccountsActions {
29
- loadAccounts: () => void;
30
- addAccount: (name: string, key: string) => Promise<boolean>;
31
- /** A newly opened addition form owns a fresh ID; retries keep its existing ID. */
32
- resetAddition?: () => void;
33
- renameAccount: (ref: string, name: string) => Promise<boolean>;
34
- removeAccount: (ref: string) => Promise<boolean>;
35
- selectAccount: (ref: string) => Promise<boolean>;
36
- setAutoSwitch: (next: boolean) => Promise<boolean>;
37
- replaceAccountKey: (ref: string, key: string) => Promise<boolean>;
38
- /** Move one account to a new position; the first row becomes the preferred account. */
39
- moveAccount: (ref: string, toIndex: number) => Promise<boolean>;
40
- }
41
- /** Immediate account operations, separate from the page's staged tuning form. */
42
- export declare class GoAccountsController {
43
- private readonly scope;
44
- private readonly ctx;
45
- private readonly readUsage;
46
- private readonly publish;
47
- private readonly blocked;
48
- private readonly execute;
49
- private rows;
50
- private busy;
51
- private refreshing;
52
- private failure;
53
- private epoch;
54
- private identity;
55
- private loaded;
56
- private disposed;
57
- private addition;
58
- constructor(scope: SettingsScope<AccountSettings & {
59
- baseURL?: string;
60
- proxyURL?: string;
61
- }>, ctx: Context, readUsage: (ref: string) => Promise<GoUsage>, publish: () => void, blocked: () => boolean, execute?: ExecuteAccountCommand);
62
- snapshot(): GoAccountsState;
63
- sync(): void;
64
- dispose(): void;
65
- invalidate(ref: string): void;
66
- refresh(): Promise<void>;
67
- /** Mark every account row as unreadable with the Host's own diagnostic. */
68
- private markUnreadable;
69
- actions(): GoAccountsActions;
70
- private run;
71
- }
@@ -1,17 +0,0 @@
1
- /**
2
- * The hint mark a label carries and the sentence behind it.
3
- *
4
- * The Host's own Tooltip opens the sentence on hover and on keyboard focus, while
5
- * the same sentence stays in the document so assistive technology reads it without
6
- * opening anything. A Host that ships no question glyph still gets a mark, and a
7
- * Host with no Tooltip keeps the sentence visible instead of hiding it.
8
- */
9
- /** A focusable help trigger; fields also retain their {@link HintText} description. */
10
- export declare function HintMark(props: {
11
- hint: string;
12
- }): import("react").JSX.Element;
13
- /** The hint sentence, at the id a control points its `aria-describedby` at. */
14
- export declare function HintText(props: {
15
- id: string;
16
- hint: string;
17
- }): import("react").JSX.Element;
@@ -1,7 +0,0 @@
1
- import type { Context } from '@deepseek-ai/cordis';
2
- import { type OpencodeGoSettings } from '../config-contract.ts';
3
- import type { SettingsScope } from './settings.ts';
4
- import type { ExecuteAccountCommand } from './accounts-controller.ts';
5
- /** Only the boundary knows whether the host exposes profile forms or legacy scopes. */
6
- export declare function mountSettingsScopes(ctx: Context, mount: (ctx: Context, scope: SettingsScope<OpencodeGoSettings>) => void): void;
7
- export declare function accountCommands(ctx: Context): ExecuteAccountCommand;
@@ -1,11 +0,0 @@
1
- /**
2
- * Host UI atoms this page reaches for across DSH generations.
3
- *
4
- * Icon names moved from pixel size (`IconQuestionOutline14`, DSH 0.1.6 and older) to
5
- * stroke weight (`IconQuestionOutlineRegular`, 0.1.7 and later), and the old alias is
6
- * gone in 0.2. A missing atom is not a cosmetic loss here: React throws on an
7
- * undefined element type, which takes the whole settings surface down with it, so
8
- * every lookup resolves the current name first and keeps the older one behind it.
9
- */
10
- /** The atom's component, or undefined when this Host ships neither name. */
11
- export declare function hostAtom<T>(host: Record<string, unknown>, current: string, previous: string): T | undefined;