dsh-grok-provider 0.1.3 → 0.1.4

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.4 - 2026-08-28
4
+
5
+ - Add an asynchronous Responses request compiler for bounded jpeg/png image input from the optional Harness attachment store.
6
+ - Preserve the exact `0.1.3` text request path when no image is present; compile ordered user and tool-result image content only before transport starts.
7
+ - Advertise image input only for exact `grok-4.6`; `grok-4.5` and all other models remain text-only.
8
+ - Enforce per-image bytes, pixels, dimensions, image count, aggregate derived bytes, a 20,000-block image-compilation budget, one-level tool-result nesting, MIME magic, cancellation, and a final 16 MiB JSON limit with deterministic oldest-first offloading.
9
+ - Keep `prompt_cache_key`, Web/X Search, image generation, URL downloads, new SSE events, authentication, and endpoint changes out of this release.
10
+ - Verify exact `grok-4.6` against the fixed CLI Chat Proxy with red and blue fixtures in both user-image and tool-result-image positions; all four streams returned HTTP 200 SSE completion and passed the normalized whole-response color assertion.
11
+ - Fail closed on `grok-4.5` image input after its controlled red-fixture Proxy response proved semantically unreliable; keep `grok-4.5` and every other model text-only.
12
+ - Fix image requests to `detail:"high"` following the official xAI Responses image example; retain real Harness `0.1.1-rc.2` attachment-local/LlmRuntime isolation as a final candidate revalidation gate.
13
+
3
14
  ## 0.1.3 - 2026-08-27
4
15
 
5
16
  - Fix existing Harness conversations failing immediately after switching from Ark to Grok when an earlier tool-call ID contains Ark's `|` delimiter.
package/CONTRIBUTING.md CHANGED
@@ -16,9 +16,9 @@
16
16
 
17
17
  ## 变更流程
18
18
 
19
- 1. 阅读 [`docs/README.md`](docs/README.md) 和与改动相关的 ADR。
20
- 2. 先写或更新测试,保持变更范围单一。
21
- 3. 认证、凭据、固定 endpoint、模型协议或发布边界变化时,先更新 ADR、威胁模型和测试计划。
19
+ 1. 阅读 [`docs/README.md`](docs/README.md)、[能力路线图](docs/11-capability-roadmap.md) 和与改动相关的 ADR。
20
+ 2. 先写或更新测试,保持变更范围单一。内容类型必须落在当前版本切片内,不得把搜索或生图并进图片输入版本。
21
+ 3. 认证、凭据、固定 endpoint、模型协议或发布边界变化时,先更新 ADR、威胁模型和测试计划。公开协议可以驱动隔离原型,但新内容类型在对外声明、合并发布基线或制作候选包前必须完成固定 CLI Chat Proxy 的脱敏 spike。
22
22
  4. 同步维护 `README.md` 与 `README.en.md` 的用户可见信息。
23
23
  5. 运行验证:
24
24
 
@@ -42,6 +42,7 @@ npm run pack:check
42
42
 
43
43
  ## 设计原则
44
44
 
45
+ - 内容类型按 [`docs/11-capability-roadmap.md`](docs/11-capability-roadmap.md) 分版本引入,不把未排期能力混进当前切片。
45
46
  - 模型能力来自动态目录,不通过隐藏未知模型制造“全部支持”的假象。
46
47
  - Renderer、RPC 与错误信息不接触凭据或身份数据。
47
48
  - 只允许固定官方网络目标,拒绝用户配置任意 base URL 和认证重定向。
@@ -50,4 +51,4 @@ npm run pack:check
50
51
 
51
52
  ---
52
53
 
53
- English summary: include exact versions, a minimal reproduction, and redacted diagnostics in bug reports. Never post credentials or personal data. Keep PRs focused, add tests first, update design/security documents for boundary changes, keep both READMEs synchronized, and run `npm test` plus `npm run pack:check` before submission.
54
+ English summary: include exact versions, a minimal reproduction, and redacted diagnostics in bug reports. Never post credentials or personal data. Keep PRs focused, add tests first, follow the capability roadmap so content-type slices stay on their assigned versions, update design/security documents for boundary changes, keep both READMEs synchronized, and run `npm test` plus `npm run pack:check` before submission.
package/README.en.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Use an already authenticated official Grok Build account from DeepSeek Harness, with dynamic model discovery, streaming reasoning, tool calls, and an account quota/model capability dashboard.
6
6
 
7
- > Unofficial community project; not affiliated with xAI or DeepSeek Harness. The current stable version is `0.1.3`. The project no longer publishes prereleases; stable defects are fixed in a new incremented stable version.
7
+ > Unofficial community project; not affiliated with xAI or DeepSeek Harness. The current source version is `0.1.4`; the published stable version is whatever the npm Registry currently assigns to `latest`. The project no longer publishes prereleases; stable defects are fixed in a new incremented stable version.
8
8
 
9
9
  ## What it provides
10
10
 
@@ -14,6 +14,7 @@ Use an already authenticated official Grok Build account from DeepSeek Harness,
14
14
  | Credentials | Reuses official CLI session state without creating a second token store |
15
15
  | Models | Discovers every model visible to the account at runtime; no static model allowlist |
16
16
  | Conversations | Streaming Responses text, reasoning, encrypted reasoning replay, usage, and finish reasons |
17
+ | Images | Only exact `grok-4.6` accepts bounded JPEG/PNG images from Harness attachments; `grok-4.5` and all other models remain text-only |
17
18
  | Tools | Returns function calls to the Harness permission layer; the provider never executes tools |
18
19
  | Account dashboard | Login status, weekly/monthly quota, reset time, dynamic model capabilities and reasoning efforts |
19
20
  | Surfaces | Bilingual Web settings and a closed `/grok` TUI command set |
@@ -38,10 +39,10 @@ The official CLI opens a browser on first use. The provider supports only the of
38
39
 
39
40
  ### 2. Install the provider
40
41
 
41
- Install the published exact version from npm:
42
+ After `0.1.4` is published, install that exact version from npm:
42
43
 
43
44
  ```sh
44
- dsh plugin --profile web add dsh-grok-provider@0.1.3
45
+ dsh plugin --profile web add dsh-grok-provider@0.1.4
45
46
  dsh web
46
47
  ```
47
48
 
@@ -111,16 +112,16 @@ Uninstalling the provider does not remove the official Grok CLI or directly modi
111
112
 
112
113
  ## Sources and discovery
113
114
 
114
- - Exact npm version: [dsh-grok-provider@0.1.3](https://www.npmjs.com/package/dsh-grok-provider/v/0.1.3)
115
- - GitHub release and integrity values: [v0.1.3](https://github.com/yoshino-xiao7/dsh-grok-provider/releases/tag/v0.1.3)
115
+ - npm `0.1.4` page (available after publication): [dsh-grok-provider@0.1.4](https://www.npmjs.com/package/dsh-grok-provider/v/0.1.4)
116
+ - GitHub `0.1.4` release and integrity values (available after publication): [v0.1.4](https://github.com/yoshino-xiao7/dsh-grok-provider/releases/tag/v0.1.4)
116
117
  - GitHub community discovery: the repository carries the DeepSeek Harness-recommended `dsh-plugin` and `dsh` topics
117
- - YukiRyou managed source: [deepseek-yukiryou-plugin-catalog](https://github.com/yoshino-xiao7/deepseek-yukiryou-plugin-catalog), following the exact verified stable version and currently marking only verified macOS arm64
118
+ - YukiRyou managed source: [deepseek-yukiryou-plugin-catalog](https://github.com/yoshino-xiao7/deepseek-yukiryou-plugin-catalog), still pinned to the real-device-accepted `dsh-grok-provider@0.1.0` and marking only `darwin-arm64`
118
119
 
119
- Directory inclusion is not an endorsement by xAI or DeepSeek Harness. The public curated directory still requires its repository-age gate and independent maintainer review.
120
+ Directory inclusion is not an endorsement by xAI or DeepSeek Harness. [Listing PR #3415](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/3415) has added the project to the public `awesome-dsh-plugin` `model` category. That directory is repository-level discovery and carries no exact npm-version or platform-acceptance claim.
120
121
 
121
122
  ## Compatibility and scope
122
123
 
123
- | Item | `0.1.3` status |
124
+ | Item | `0.1.4` status |
124
125
  | --- | --- |
125
126
  | DeepSeek Harness | Exact support for `0.1.1-rc.2` |
126
127
  | Node.js | `>=24.19.0` |
@@ -130,7 +131,9 @@ Directory inclusion is not an endorsement by xAI or DeepSeek Harness. The public
130
131
  | Grok CLI | No full-version lock; official path, `login --oauth` capability, and production OIDC credential contract are enforced |
131
132
  | Models | Every account catalog model whose backend has a strict codec in this release |
132
133
 
133
- The initial release excludes image input, Web/X Search, arbitrary downloads, API-key mode, multiple accounts, enterprise OIDC, ACP, and Headless agent wrapping. See the complete [product requirements](docs/01-product-requirements.md).
134
+ `0.1.4` enables image input only for exact `grok-4.6`; `grok-4.5` and every other dynamically discovered model remain text-only. Images must be verified JPEG/PNG projections from the Harness attachment service. Ordinary user content and images nested one level inside a tool result are supported, with `detail:"high"` fixed to the official xAI Responses image example; URLs, filesystem paths, file IDs, and caller-supplied data URLs are rejected.
135
+
136
+ Each projected image is limited to 4 MiB, 16,777,216 pixels, and 8192px per side. A request retains at most eight images and 8 MiB of projected image bytes. When a limit is exceeded, the globally oldest images are offloaded to Harness text placeholders; the final JSON remains capped at 16 MiB. Web/X Search, image generation, arbitrary downloads, API-key mode, multiple accounts, enterprise OIDC, ACP, and Headless agent wrapping remain out of scope; see the [capability roadmap](docs/11-capability-roadmap.md).
134
137
 
135
138
  ## How it works
136
139
 
@@ -147,7 +150,7 @@ dsh-grok-provider Host
147
150
  xAI Grok Build
148
151
  ```
149
152
 
150
- Model IDs come from the runtime catalog rather than a hardcoded list. If an account exposes a new backend that cannot be mapped safely, discovery fails closed instead of hiding the model and claiming complete support.
153
+ Model IDs come from the runtime catalog rather than a hardcoded list. Image modality is enabled only for exact model IDs backed by separate protocol and live evidence. If an account exposes a new backend that cannot be mapped safely, discovery fails closed instead of hiding the model and claiming complete support.
151
154
 
152
155
  ## Security and privacy
153
156
 
@@ -156,7 +159,7 @@ Model IDs come from the runtime catalog rather than a hardcoded list. If an acco
156
159
  - The Host must perform a bounded read of the official `auth.json`, whose raw file may contain a refresh token. The parser does not use, cache, or persist that refresh token; it retains only validation metadata and a short-lived access-token lease.
157
160
  - The provider does not implement a refresh grant. Near expiry it may invoke one bounded official `grok models`, then reread and revalidate the official credential file.
158
161
  - Login subprocesses use fixed argv, a scrubbed environment, output limits, deadlines, cancellation, and no shell.
159
- - Prompts and tool results are sent to the xAI Grok Build service; the provider itself does not log them.
162
+ - Prompts, tool results, and image projections selected for a request are sent to the xAI Grok Build service; the provider itself does not log that content, source images, or projected bytes.
160
163
 
161
164
  See the full [threat model](docs/03-security-threat-model.md). For vulnerabilities, read the [security policy](SECURITY.md) and never post tokens, `auth.json`, personal data, or full diagnostic logs in a public issue.
162
165
 
@@ -180,7 +183,7 @@ A protobuf-omitted zero is restored only with a complete typed period. In every
180
183
 
181
184
  ### An existing conversation fails immediately after switching from another model to Grok
182
185
 
183
- Versions through `0.1.2` could not convert some third-party tool-call histories containing special characters, notably `|` in Ark call IDs. Update to `0.1.3`; it preserves call/result correlation while safely mapping incompatible historical IDs before sending the request to Grok.
186
+ Versions through `0.1.2` could not convert some third-party tool-call histories containing special characters, notably `|` in Ark call IDs. Update to `0.1.3` or later; it preserves call/result correlation while safely mapping incompatible historical IDs before sending the request to Grok.
184
187
 
185
188
  ### Does Windows work?
186
189
 
@@ -202,6 +205,7 @@ Project map:
202
205
  - [`docs/04-harness-contract.md`](docs/04-harness-contract.md): Harness integration contract;
203
206
  - [`docs/05-test-plan.md`](docs/05-test-plan.md): platform, security, and release gates;
204
207
  - [`docs/09-implementation-status.md`](docs/09-implementation-status.md): implementation and acceptance status;
208
+ - [`docs/11-capability-roadmap.md`](docs/11-capability-roadmap.md): content-type sequence from `0.1.4`;
205
209
  - [`CHANGELOG.md`](CHANGELOG.md): version history.
206
210
 
207
211
  Read the [contributing guide](CONTRIBUTING.md) before filing an issue or PR. Changes to authentication, transport, credential formats, or release boundaries must update the relevant ADR/threat model before implementation and tests.
@@ -215,10 +219,12 @@ Read the [contributing guide](CONTRIBUTING.md) before filing an issue or PR. Cha
215
219
  - [x] Publish the `0.1.1` documentation and release-process correction
216
220
  - [x] Publish the `0.1.2` Windows CLI compatibility correction
217
221
  - [x] Publish the `0.1.3` cross-provider tool-history compatibility correction
222
+ - [x] `0.1.4`: image input only for exact `grok-4.6`; red/blue user and tool-result Proxy gates plus final Harness attachment revalidation pass, while `grok-4.5` fails closed as text-only
223
+ - [ ] Later independent slice: opt-in, default-off Web Search / X Search
224
+ - [ ] A subsequent slice: opt-in image generation (inline results only, committed through Harness attachments)
218
225
  - [ ] Complete independent Windows x64 acceptance and publish a later stable fix if needed
219
- - [ ] Evaluate additional content types and platforms only against verified Harness/xAI contracts
220
226
 
221
- The roadmap is not a compatibility promise; every new capability must pass the documented design and security gates.
227
+ Slice details, gates, and permanent non-goals are in the [capability roadmap](docs/11-capability-roadmap.md). The roadmap is not a compatibility promise; each capability needs its own ADR and security gates. `prompt_cache_key` is not bundled with image input. Arbitrary URL downloads and API-key mode stay out of scope.
222
228
 
223
229
  ## License
224
230
 
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  让 DeepSeek Harness 使用你已登录的官方 Grok Build 账号:动态模型发现、流式推理、工具调用,以及账号额度与模型能力面板。
6
6
 
7
- > 非官方社区项目,与 xAI 或 DeepSeek Harness 官方无隶属关系。当前稳定版本为 `0.1.3`。项目不再发行预发行版;正式版缺陷通过新的递增稳定版本修复。
7
+ > 非官方社区项目,与 xAI 或 DeepSeek Harness 官方无隶属关系。当前源码版本为 `0.1.4`;npm 已发布稳定版本以 Registry 的 `latest` 标签为准。项目不再发行预发行版;正式版缺陷通过新的递增稳定版本修复。
8
8
 
9
9
  ## 它解决什么问题
10
10
 
@@ -14,6 +14,7 @@
14
14
  | 凭据 | 复用官方 CLI 的登录状态;插件不创建第二份 token 存储 |
15
15
  | 模型 | 运行时读取账号可见的全部 Grok Build 模型,不维护静态模型白名单 |
16
16
  | 对话 | Responses 流式文本、reasoning、加密 reasoning replay、usage 与 finish reason |
17
+ | 图片 | 仅精确 `grok-4.6` 接收 Harness attachment 中有界的 JPEG/PNG 图片;`grok-4.5` 与其他模型保持 text-only |
17
18
  | 工具 | 将 function call 交回 Harness 权限层;Provider 本身不执行工具 |
18
19
  | 账户面板 | 登录状态、每周/月额度、重置时间、动态模型能力与 reasoning 档位 |
19
20
  | 界面 | Web 设置页中英文切换;TUI 提供闭合的 `/grok` 命令 |
@@ -38,10 +39,10 @@ grok models
38
39
 
39
40
  ### 2. 安装 Provider
40
41
 
41
- 从 npm 安装已发布的精确版本:
42
+ `0.1.4` 发布后,从 npm 安装该精确版本:
42
43
 
43
44
  ```sh
44
- dsh plugin --profile web add dsh-grok-provider@0.1.3
45
+ dsh plugin --profile web add dsh-grok-provider@0.1.4
45
46
  dsh web
46
47
  ```
47
48
 
@@ -111,16 +112,16 @@ dsh web
111
112
 
112
113
  ## 项目来源与发现
113
114
 
114
- - npm 精确版本:[dsh-grok-provider@0.1.3](https://www.npmjs.com/package/dsh-grok-provider/v/0.1.3)
115
- - GitHub 发行版与校验值:[v0.1.3](https://github.com/yoshino-xiao7/dsh-grok-provider/releases/tag/v0.1.3)
115
+ - npm `0.1.4` 页面(发布后可用):[dsh-grok-provider@0.1.4](https://www.npmjs.com/package/dsh-grok-provider/v/0.1.4)
116
+ - GitHub `0.1.4` 发行版与校验值(发布后可用):[v0.1.4](https://github.com/yoshino-xiao7/dsh-grok-provider/releases/tag/v0.1.4)
116
117
  - GitHub 社区发现:仓库已添加 DeepSeek Harness 官方推荐的 `dsh-plugin` 与 `dsh` Topics
117
- - YukiRyou 受管来源:[deepseek-yukiryou-plugin-catalog](https://github.com/yoshino-xiao7/deepseek-yukiryou-plugin-catalog),跟随已验证的精确稳定版本,当前只标记已验证的 macOS arm64
118
+ - YukiRyou 受管来源:[deepseek-yukiryou-plugin-catalog](https://github.com/yoshino-xiao7/deepseek-yukiryou-plugin-catalog),当前仍锁定已完成真机验收的 `dsh-grok-provider@0.1.0`,且只标记 `darwin-arm64`
118
119
 
119
- 出现在目录中不代表 xAI 或 DeepSeek Harness 官方背书。公共 curated 目录仍需满足其仓库年龄门槛并通过独立维护者评审。
120
+ 出现在目录中不代表 xAI 或 DeepSeek Harness 官方背书。项目已通过[收录 PR #3415](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/3415) 进入公共 `awesome-dsh-plugin` 的 `model` 分类;该目录是仓库级发现入口,不承载精确 npm 版本或平台验收声明。
120
121
 
121
122
  ## 兼容性与范围
122
123
 
123
- | 项目 | `0.1.3` 状态 |
124
+ | 项目 | `0.1.4` 状态 |
124
125
  | --- | --- |
125
126
  | DeepSeek Harness | 精确支持 `0.1.1-rc.2` |
126
127
  | Node.js | `>=24.19.0` |
@@ -130,7 +131,9 @@ dsh web
130
131
  | Grok CLI | 不锁完整版本;严格校验官方路径、`login --oauth` 能力与生产 OIDC 凭据契约 |
131
132
  | 模型 | 当前账号目录中 backend 已被严格 codec 支持的全部模型 |
132
133
 
133
- 首版不包含图片输入、Web/X Search、任意文件下载、API Key 模式、多账号、企业 OIDC、ACP 或 Headless agent 封装。完整范围见[产品需求](docs/01-product-requirements.md)。
134
+ `0.1.4` 只为精确的 `grok-4.6` 开启图片输入;`grok-4.5` 与其他动态发现的模型继续按 text-only 处理。图片只能来自 Harness attachment service 的已验证 JPEG/PNG 投影,支持普通用户内容和一层工具结果中的图片,并按 xAI 官方 Responses 图片示例固定使用 `detail:"high"`;不接受 URL、文件路径、file ID 或调用方预制的 data URL。
135
+
136
+ 每张投影图片最多 4 MiB、16,777,216 像素且任一边不超过 8192px;每次请求最多保留 8 张、投影字节合计最多 8 MiB。超限时按全局最旧优先移除图片并保留 Harness 的文本占位,最终 JSON 仍受 16 MiB 上限约束。Web/X Search、图片生成、任意文件下载、API Key 模式、多账号、企业 OIDC、ACP 与 Headless agent 封装仍不在本版本范围内;后续切片见[能力路线图](docs/11-capability-roadmap.md)。
134
137
 
135
138
  ## 工作原理
136
139
 
@@ -147,7 +150,7 @@ dsh-grok-provider Host
147
150
  xAI Grok Build
148
151
  ```
149
152
 
150
- 模型 ID 来自运行时目录,不是硬编码列表。如果账号出现当前版本无法安全映射的新 backend,发现过程会失败关闭,而不是隐藏模型后宣称“全部支持”。
153
+ 模型 ID 来自运行时目录,不是硬编码列表;图片 modality 则只对有独立协议与真机证据的精确模型 ID 开启。如果账号出现当前版本无法安全映射的新 backend,发现过程会失败关闭,而不是隐藏模型后宣称“全部支持”。
151
154
 
152
155
  ## 安全与隐私
153
156
 
@@ -156,7 +159,7 @@ dsh-grok-provider Host
156
159
  - Host 必须有界读取官方 `auth.json`,其原始文件可能包含 refresh token;解析器不使用、不缓存、不持久化 refresh token,只保留闭合校验所需元数据与短期 access-token lease。
157
160
  - 插件不实现 refresh grant;凭据临近过期时,只能有界调用一次官方 `grok models`,再重新读取并验证官方文件。
158
161
  - 登录子进程使用固定 argv、过滤后的环境、输出上限、deadline 与取消处理,不通过 shell 启动。
159
- - 提示词和工具结果会发送给 xAI Grok Build 服务;插件本身不把它们写入日志。
162
+ - 提示词、工具结果以及用户选择发送的图片投影会发往 xAI Grok Build 服务;插件本身不记录这些内容、原图或投影字节。
160
163
 
161
164
  完整边界见[威胁模型](docs/03-security-threat-model.md)。发现安全问题时,请阅读[安全策略](SECURITY.md),不要在公开 Issue 中提交 token、`auth.json`、个人信息或完整诊断日志。
162
165
 
@@ -180,7 +183,7 @@ dsh-grok-provider Host
180
183
 
181
184
  ### 从其他模型切换到 Grok 后立即提示响应无效
182
185
 
183
- `0.1.2` 及更早版本不能转换部分包含特殊字符的第三方工具调用历史,典型情况是 Ark 调用 ID 中的 `|`。请更新到 `0.1.3`;新版本会保持工具调用与结果的关联,并在发送给 Grok 前安全映射不兼容的历史 ID。
186
+ `0.1.2` 及更早版本不能转换部分包含特殊字符的第三方工具调用历史,典型情况是 Ark 调用 ID 中的 `|`。请更新到 `0.1.3` 或更高版本;新版本会保持工具调用与结果的关联,并在发送给 Grok 前安全映射不兼容的历史 ID。
184
187
 
185
188
  ### Windows 能用吗
186
189
 
@@ -202,6 +205,7 @@ npm run pack:check
202
205
  - [`docs/04-harness-contract.md`](docs/04-harness-contract.md):Harness 集成契约;
203
206
  - [`docs/05-test-plan.md`](docs/05-test-plan.md):平台、安全与发行门禁;
204
207
  - [`docs/09-implementation-status.md`](docs/09-implementation-status.md):实现与验收状态;
208
+ - [`docs/11-capability-roadmap.md`](docs/11-capability-roadmap.md):`0.1.4` 起的内容类型路线;
205
209
  - [`CHANGELOG.md`](CHANGELOG.md):版本变化。
206
210
 
207
211
  提交 Issue 或 PR 前请阅读[贡献指南](CONTRIBUTING.md)。认证、传输、凭据格式或发布边界的变化必须先更新对应 ADR/威胁模型,再开发和测试。
@@ -215,10 +219,12 @@ npm run pack:check
215
219
  - [x] 发布 `0.1.1` 文档与发布流程修正版
216
220
  - [x] 发布 `0.1.2` Windows CLI 兼容性修正版
217
221
  - [x] 发布 `0.1.3` 跨 Provider 工具调用历史兼容性修正版
222
+ - [x] `0.1.4`:仅精确 `grok-4.6` 图片输入;user/tool-result 红蓝语义 Proxy 门禁与最终 Harness attachment 复验均已通过,`grok-4.5` 已按失败关闭保持 text-only
223
+ - [ ] 后续独立切片:默认关闭、用户分别开启的 Web Search / X Search
224
+ - [ ] 再后续独立切片:默认关闭的图片生成(只收内联结果,提交 Harness attachment)
218
225
  - [ ] 完成 Windows x64 独立真机验收并按需发布后续稳定修复版
219
- - [ ] 根据已验证的 Harness/xAI 协议逐项评估更多内容类型和平台
220
226
 
221
- 路线图不是兼容性承诺;新增能力必须通过文档决策与安全门禁。
227
+ 完整切片、门禁与永久非目标见[能力路线图](docs/11-capability-roadmap.md)。路线图不是兼容性承诺;新增能力必须通过独立 ADR 与安全门禁。`prompt_cache_key` 不与图片输入捆绑;不引入任意 URL 下载或 API Key 模式。
222
228
 
223
229
  ## 许可证
224
230
 
package/SECURITY.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## 支持范围
6
6
 
7
- 当前稳定版本为 `0.1.3`。DeepSeek Harness、Node.js 与操作系统按发布线明确维护;Grok CLI 不使用完整版本字符串作为信任门禁,而是严格校验官方默认路径、命令能力、生产 OIDC 凭据契约和固定服务端协议。macOS arm64 已完成真实验收;Windows x64 有代码与 CI 覆盖。`0.1.3` 只修复有界历史调用 ID 的请求内安全映射,不改动认证、凭据或平台执行边界。项目不再发行预发行版,安全或兼容性缺陷使用新的递增稳定版本修复。
7
+ 本安全策略对应源码版本 `0.1.4`;当前已发布稳定版本以 npm Registry 的 `latest` 标签为准。DeepSeek Harness、Node.js 与操作系统按发布线明确维护;Grok CLI 不使用完整版本字符串作为信任门禁,而是严格校验官方默认路径、命令能力、生产 OIDC 凭据契约和固定服务端协议。macOS arm64 已完成真实验收;Windows x64 有代码与 CI 覆盖。`0.1.4` 只为精确 `grok-4.6` 增加有界图片输入;`grok-4.5` 与所有其他模型保持 text-only,不改动认证、凭据、CLI subprocess 或 endpoint 边界。图片只能来自 Harness attachment service 的已验证 JPEG/PNG 投影,以 `detail:"high"` 发送,并受单图字节、像素、边长、数量、总字节与最终 JSON 上限约束;URL、路径、file ID 和调用方预制 data URL 都会被拒绝。项目不再发行预发行版,安全或兼容性缺陷使用新的递增稳定版本修复。
8
8
 
9
9
  ## 私下报告漏洞
10
10
 
@@ -17,7 +17,7 @@
17
17
  - 已脱敏的请求/响应形状或错误码;
18
18
  - 建议修复方向(如有)。
19
19
 
20
- 绝对不要发送真实 `auth.json`、access/refresh token、`user_id`、Cookie、邮箱、姓名、完整提示词、工具参数或包含这些数据的诊断包。若秘密已暴露,请先通过官方 Grok CLI 注销/重新登录并按相应服务流程撤销凭据。
20
+ 绝对不要发送真实 `auth.json`、access/refresh token、`user_id`、Cookie、邮箱、姓名、完整提示词、工具参数、原图/投影图片字节或包含这些数据的诊断包。若秘密已暴露,请先通过官方 Grok CLI 注销/重新登录并按相应服务流程撤销凭据。
21
21
 
22
22
  ## 不属于漏洞的情况
23
23
 
@@ -32,4 +32,4 @@
32
32
 
33
33
  ---
34
34
 
35
- English summary: GitHub Private vulnerability reporting is enabled and is the preferred reporting channel. If it is unavailable, open only a detail-free contact issue. Never publish or send real credentials, identity data, prompts, tool arguments, cookies, or unreviewed diagnostic archives. Include exact versions, platform, minimal reproduction conditions, impact, and redacted evidence.
35
+ English summary: GitHub Private vulnerability reporting is enabled and is the preferred reporting channel. Version `0.1.4` enables bounded JPEG/PNG attachment projections with `detail:"high"` only for exact `grok-4.6`; `grok-4.5` and all other models remain text-only. It accepts no URL, path, file ID, or caller-supplied data URL and does not change authentication or endpoints. If private reporting is unavailable, open only a detail-free contact issue. Never publish or send real credentials, identity data, prompts, tool arguments, cookies, source/projected image bytes, or unreviewed diagnostic archives. Include exact versions, platform, minimal reproduction conditions, impact, and redacted evidence.
@@ -2,26 +2,22 @@ import os from "node:os"
2
2
  import path from "node:path"
3
3
  import { randomUUID } from "node:crypto"
4
4
 
5
- import { LlmError, attributionHeaders } from "@deepseek-ai/dsh-llm"
5
+ import { attributionHeaders } from "@deepseek-ai/dsh-llm"
6
6
  import Schema from "@deepseek-ai/schemastery"
7
7
 
8
8
  import {
9
- CredentialFileTooLargeError,
10
9
  GROK_PRODUCTION_OIDC_AUTH_CONTRACT,
11
10
  UnsupportedCredentialError,
12
11
  createCredentialSource,
13
12
  } from "../internal/credential-source.mjs"
14
- import { AuthModeUnavailableError } from "../internal/auth-registry.mjs"
15
13
  import { createAccountDashboard } from "../internal/account-dashboard.mjs"
16
14
  import { createAuthController } from "../internal/auth-controller.mjs"
17
15
  import { createAuthRpcHandler } from "../internal/auth-rpc.mjs"
18
16
  import { createGrokAdapter } from "../internal/grok-adapter.mjs"
19
17
  import { createGrokCommandHandler } from "../internal/grok-command-handler.mjs"
20
- import { GrokTransportError, createGrokTransport } from "../internal/grok-transport.mjs"
21
- import {
22
- OfficialCredentialFileError,
23
- createOfficialCredentialLoader,
24
- } from "../internal/official-credential-loader.mjs"
18
+ import { createGrokTransport } from "../internal/grok-transport.mjs"
19
+ import { mapLlmError } from "../internal/llm-error.mjs"
20
+ import { createOfficialCredentialLoader } from "../internal/official-credential-loader.mjs"
25
21
  import { createOfficialCliAuth } from "../internal/official-cli-auth.mjs"
26
22
  import { createOfficialAuthDriver } from "../internal/official-auth-driver.mjs"
27
23
  import { verifyOfficialCliExecutable } from "../internal/official-cli-verifier.mjs"
@@ -64,6 +60,7 @@ export function apply(ctx) {
64
60
  }),
65
61
  createAdapter: ({ getGeneration }) => createGrokAdapter({
66
62
  getGeneration,
63
+ getAttachmentStore: () => ctx.get("attachments"),
67
64
  mapError: mapLlmError,
68
65
  }),
69
66
  })
@@ -117,31 +114,3 @@ export function apply(ctx) {
117
114
 
118
115
  ctx.effect(() => () => runtime.dispose(), "llm-grok runtime")
119
116
  }
120
-
121
- function mapLlmError(error) {
122
- if (error instanceof LlmError) return error
123
- if (error?.name === "AbortError") {
124
- return new LlmError("The Grok Build request was cancelled", "ABORTED", { cause: error })
125
- }
126
- if (
127
- error instanceof AuthModeUnavailableError ||
128
- error instanceof UnsupportedCredentialError ||
129
- error instanceof CredentialFileTooLargeError ||
130
- error instanceof OfficialCredentialFileError ||
131
- (error instanceof GrokTransportError && (error.status === 401 || error.status === 403))
132
- ) {
133
- return new LlmError("Grok authentication is required", "AUTH", {
134
- cause: error,
135
- ...(error.status === undefined ? {} : { status: error.status }),
136
- })
137
- }
138
- if (error instanceof GrokTransportError) {
139
- return new LlmError("The Grok Build request failed", error.status === 429 ? "RATE_LIMIT" : "PROVIDER_ERROR", {
140
- cause: error,
141
- ...(error.status === undefined ? {} : { status: error.status }),
142
- })
143
- }
144
- return new LlmError("The Grok provider rejected an invalid or unsupported response", "INVALID_RESPONSE", {
145
- cause: error,
146
- })
147
- }
@@ -1,6 +1,6 @@
1
1
  import { parseModelCatalogResponse } from "./model-catalog.mjs"
2
2
  import { createResponsesEventDecoder } from "./responses-codec.mjs"
3
- import { encodeResponsesRequest } from "./responses-request.mjs"
3
+ import { createResponsesRequestCompiler } from "./responses-request-compiler.mjs"
4
4
  import { parseResponsesSse } from "./responses-sse.mjs"
5
5
 
6
6
  export class GrokAdapterError extends Error {
@@ -10,11 +10,16 @@ export class GrokAdapterError extends Error {
10
10
  }
11
11
  }
12
12
 
13
- export function createGrokAdapter({ getGeneration, mapError = (error) => error }) {
13
+ export function createGrokAdapter({
14
+ getGeneration,
15
+ getAttachmentStore = () => undefined,
16
+ mapError = (error) => error,
17
+ }) {
14
18
  if (typeof getGeneration !== "function") {
15
19
  throw new TypeError("A Grok adapter generation source is required")
16
20
  }
17
21
  if (typeof mapError !== "function") throw new TypeError("Invalid Grok adapter error mapper")
22
+ const requestCompiler = createResponsesRequestCompiler({ getAttachmentStore })
18
23
 
19
24
  const captureGeneration = () => {
20
25
  const generation = getGeneration()
@@ -29,17 +34,13 @@ export function createGrokAdapter({ getGeneration, mapError = (error) => error }
29
34
  return generation
30
35
  }
31
36
 
32
- const resolveWithGeneration = async (generation, provider, model, signal) => {
33
- try {
34
- requireProvider(provider)
35
- if (typeof model !== "string" || model.length === 0) throw new GrokAdapterError()
36
- const entries = await discover(generation, provider, signal)
37
- const match = entries.find((entry) => entry.resolvedModelInfo.id === model)
38
- if (match === undefined) throw new GrokAdapterError()
39
- return match.resolvedModelInfo
40
- } catch (error) {
41
- throw mapError(error)
42
- }
37
+ const resolveRouteWithGeneration = async (generation, provider, model, signal) => {
38
+ requireProvider(provider)
39
+ if (typeof model !== "string" || model.length === 0) throw new GrokAdapterError()
40
+ const entries = await discover(generation, provider, signal)
41
+ const match = entries.find((entry) => entry.resolvedModelInfo.id === model)
42
+ if (match === undefined) throw new GrokAdapterError()
43
+ return match
43
44
  }
44
45
 
45
46
  return Object.freeze({
@@ -64,30 +65,43 @@ export function createGrokAdapter({ getGeneration, mapError = (error) => error }
64
65
  },
65
66
 
66
67
  async resolveModel(provider, model, signal) {
67
- return resolveWithGeneration(captureGeneration(), provider, model, signal)
68
+ try {
69
+ const route = await resolveRouteWithGeneration(captureGeneration(), provider, model, signal)
70
+ return route.resolvedModelInfo
71
+ } catch (error) {
72
+ throw mapError(error, signal)
73
+ }
68
74
  },
69
75
 
70
76
  async prepareCall(provider, model, signal) {
71
- const generation = captureGeneration()
72
- const resolvedModel = await resolveWithGeneration(generation, provider, model, signal)
73
- return Object.freeze({
74
- model: resolvedModel,
75
- stream(options) {
76
- try {
77
- validatePreparedOptions(options, resolvedModel)
78
- return streamWithGeneration(generation, options, mapError)
79
- } catch (error) {
80
- throw mapError(error)
81
- }
82
- },
83
- })
77
+ try {
78
+ const generation = captureGeneration()
79
+ const route = await resolveRouteWithGeneration(generation, provider, model, signal)
80
+ return Object.freeze({
81
+ model: route.resolvedModelInfo,
82
+ stream(options) {
83
+ let requestPlan
84
+ try {
85
+ requestPlan = requestCompiler.prepare(options)
86
+ validatePreparedRequestPlan(requestPlan, route.resolvedModelInfo)
87
+ return streamWithGeneration(generation, route, requestPlan, mapError)
88
+ } catch (error) {
89
+ throw mapError(error, requestPlan?.signal ?? readOwnDataSignal(options))
90
+ }
91
+ },
92
+ })
93
+ } catch (error) {
94
+ throw mapError(error, signal)
95
+ }
84
96
  },
85
97
 
86
98
  stream(options) {
99
+ let requestPlan
87
100
  try {
88
- return streamWithGeneration(captureGeneration(), options, mapError)
101
+ requestPlan = requestCompiler.prepare(options)
102
+ return streamWithGeneration(captureGeneration(), undefined, requestPlan, mapError)
89
103
  } catch (error) {
90
- throw mapError(error)
104
+ throw mapError(error, requestPlan?.signal ?? readOwnDataSignal(options))
91
105
  }
92
106
  },
93
107
  })
@@ -98,37 +112,66 @@ async function discover(generation, provider, signal) {
98
112
  return parseModelCatalogResponse(raw, { provider }).map((entry) => Object.freeze({
99
113
  backend: entry.backend,
100
114
  resolvedModelInfo: freezeModel(entry.resolvedModelInfo),
115
+ ...(entry.imageInput === undefined ? {} : { imageInput: entry.imageInput }),
101
116
  }))
102
117
  }
103
118
 
104
- async function* streamWithGeneration(generation, options, mapError) {
119
+ async function* streamWithGeneration(generation, preparedRoute, requestPlan, mapError) {
105
120
  try {
106
- requireProvider(options?.provider)
107
- const request = encodeResponsesRequest(options)
121
+ requireProvider(requestPlan.provider)
122
+ const route = preparedRoute ?? await resolveRoute(generation, requestPlan)
123
+ const request = await requestPlan.compile(route)
108
124
  const decoder = createResponsesEventDecoder()
109
125
  for await (const event of parseResponsesSse(
110
- generation.transport.streamResponses(request, { signal: options.signal }),
126
+ generation.transport.streamResponses(request, { signal: requestPlan.signal }),
111
127
  )) {
112
128
  for (const chunk of decoder.push(event)) yield chunk
113
129
  }
114
130
  decoder.finish()
115
131
  } catch (error) {
116
- throw mapError(error)
132
+ throw mapError(error, requestPlan.signal)
117
133
  }
118
134
  }
119
135
 
120
- function validatePreparedOptions(options, resolvedModel) {
121
- if (!isPlainObject(options) || options.provider !== "grok" || options.model !== resolvedModel.id) {
136
+ async function resolveRoute(generation, options) {
137
+ if (typeof options?.model !== "string" || options.model.length === 0) {
122
138
  throw new GrokAdapterError()
123
139
  }
124
- if (options.reasoningEffort !== undefined) {
140
+ const entries = await discover(generation, options.provider, options.signal)
141
+ const route = entries.find((entry) => entry.resolvedModelInfo.id === options.model)
142
+ if (route === undefined) throw new GrokAdapterError()
143
+ return route
144
+ }
145
+
146
+ function validatePreparedRequestPlan(requestPlan, resolvedModel) {
147
+ if (
148
+ !isPlainObject(requestPlan) ||
149
+ requestPlan.provider !== "grok" ||
150
+ requestPlan.model !== resolvedModel.id
151
+ ) {
152
+ throw new GrokAdapterError()
153
+ }
154
+ if (requestPlan.reasoningEffort !== undefined) {
125
155
  const efforts = resolvedModel.reasoning?.efforts
126
- if (!Array.isArray(efforts) || !efforts.some((effort) => effort.id === options.reasoningEffort)) {
156
+ if (
157
+ !Array.isArray(efforts) ||
158
+ !efforts.some((effort) => effort.id === requestPlan.reasoningEffort)
159
+ ) {
127
160
  throw new GrokAdapterError()
128
161
  }
129
162
  }
130
163
  }
131
164
 
165
+ function readOwnDataSignal(options) {
166
+ try {
167
+ if (!isPlainObject(options)) return undefined
168
+ const descriptor = Object.getOwnPropertyDescriptor(options, "signal")
169
+ return descriptor !== undefined && "value" in descriptor ? descriptor.value : undefined
170
+ } catch {
171
+ return undefined
172
+ }
173
+ }
174
+
132
175
  function freezeModel(model) {
133
176
  const frozen = {
134
177
  ...model,
@@ -0,0 +1,61 @@
1
+ import { LlmError } from "@deepseek-ai/dsh-llm"
2
+
3
+ import { AuthModeUnavailableError } from "./auth-registry.mjs"
4
+ import {
5
+ CredentialFileTooLargeError,
6
+ UnsupportedCredentialError,
7
+ } from "./credential-source.mjs"
8
+ import { GrokTransportError } from "./grok-transport.mjs"
9
+ import { OfficialCredentialFileError } from "./official-credential-loader.mjs"
10
+ import { UnsupportedImageInputError } from "./responses-request-compiler.mjs"
11
+
12
+ export function mapLlmError(error, signal) {
13
+ if (signal?.aborted || error?.name === "AbortError") {
14
+ return new LlmError("The Grok Build request was cancelled", "ABORTED", { cause: error })
15
+ }
16
+ if (error instanceof LlmError) return error
17
+ if (
18
+ error instanceof AuthModeUnavailableError ||
19
+ error instanceof UnsupportedCredentialError ||
20
+ error instanceof CredentialFileTooLargeError ||
21
+ error instanceof OfficialCredentialFileError ||
22
+ (error instanceof GrokTransportError && (error.status === 401 || error.status === 403))
23
+ ) {
24
+ return new LlmError("Grok authentication is required", "AUTH", {
25
+ cause: error,
26
+ ...(error.status === undefined ? {} : { status: error.status }),
27
+ })
28
+ }
29
+ if (error instanceof GrokTransportError) {
30
+ return new LlmError("The Grok Build request failed", error.status === 429 ? "RATE_LIMIT" : "PROVIDER_ERROR", {
31
+ cause: error,
32
+ ...(error.status === undefined ? {} : { status: error.status }),
33
+ })
34
+ }
35
+ if (
36
+ error instanceof UnsupportedImageInputError ||
37
+ isUnsupportedAttachmentError(error)
38
+ ) {
39
+ return new LlmError("The Grok model cannot accept this image input", "UNSUPPORTED_CONTENT", {
40
+ cause: error,
41
+ })
42
+ }
43
+ return new LlmError("The Grok provider rejected an invalid or unsupported response", "INVALID_RESPONSE", {
44
+ cause: error,
45
+ })
46
+ }
47
+
48
+ function isUnsupportedAttachmentError(error) {
49
+ return error instanceof Error && [
50
+ "TOO_MANY_IMAGES",
51
+ "IMAGES_TOO_LARGE",
52
+ "UNSUPPORTED_IMAGE_TYPE",
53
+ "INVALID_IMAGE_BASE64",
54
+ "INVALID_IMAGE",
55
+ "IMAGE_TYPE_MISMATCH",
56
+ "IMAGE_TOO_LARGE",
57
+ "IMAGE_TOO_MANY_PIXELS",
58
+ "IMAGE_DIMENSION_TOO_LARGE",
59
+ "ATTACHMENT_PROJECTION_UNSUPPORTED",
60
+ ].includes(error.code)
61
+ }