# 0004 覆盖性复查
## 结论
结论:`openai-rs` 现在已经覆盖了 `openai-node` 的绝大多数资源入口和主链路能力,但仍然没有做到完全 1:1。
和上一次检查相比,旧文档里列出的几项核心缺口已经基本补齐:
- Assistants / Beta Threads 的 `stream` / `poll` helper 已落地
- Responses SSE continuation、`starting_after` 和最终快照聚合已落地
- Chat 的 `parse`、`finish_reason` 错误、tool arguments 解析、`run_tools().send_streaming()` 已落地
- `from_env()`、自定义 `reqwest::Client`、`OPENAI_LOG` / `logger` / `log_level` 已落地
- 动态路径参数安全编码已经补到主要资源面
- Realtime / Responses WebSocket、Realtime `client_secrets` / `calls`、Beta Realtime `sessions` / `transcription_sessions` 已有实现
- Chat / Responses / Assistants 的 typed runtime event stream 已落地
- `ChatCompletionRunner` / `ChatCompletionStreamingRunner` trace 对象已落地
- 独立 `OpenAIRealtimeWebSocket` / `OpenAIRealtimeWS` / `OpenAIResponsesWebSocket` 已落地
- 统一上传 helper `to_file()` / `ToFileInput` 已落地
因此,当前更准确的判断是:
- 如果标准是“OpenAI 兼容 REST / SSE / WebSocket 主链路是否可用”,`openai-rs` 已经很接近完整。
- 如果标准是“公开 API 形态、helper 语义、运行时体验、测试广度都和 `openai-node` 对齐”,`openai-rs` 仍然有差距。
本次复查是静态对照检查,没有重新执行 `cargo test`。
## 对照范围
本次主要对照了这些目录:
- `openai-node/src/resources/*`
- `openai-node/src/lib/*`
- `openai-node/src/realtime/*`
- `openai-node/src/internal/to-file.ts`
- `openai-node/tests/*`
- `openai-rs/src/client.rs`
- `openai-rs/src/resources/*`
- `openai-rs/src/stream/mod.rs`
- `openai-rs/src/websocket/mod.rs`
- `openai-rs/src/files/mod.rs`
- `openai-rs/tests/*`
判断口径分成三层:
1. 资源入口和底层 endpoint 是否存在
2. `openai-node` 的高层 helper / runtime 语义是否有等价实现
3. tests 是否覆盖了同类风险面
## 已覆盖部分
### 1. 资源入口层面已基本齐全
按 `openai-node/src/resources` 对照,`openai-rs` 当前已经覆盖这些顶层命名空间:
- `audio`
- `batches`
- `beta`
- `chat`
- `completions`
- `containers`
- `conversations`
- `embeddings`
- `evals`
- `files`
- `fine_tuning`
- `graders`
- `images`
- `models`
- `moderations`
- `realtime`
- `responses`
- `skills`
- `uploads`
- `vector_stores`
- `videos`
- `webhooks`
虽然 `openai-rs/src/resources` 仍然采用“少文件聚合实现”的结构,而不是像 `openai-node` 那样按资源细拆,但从资源入口角度看,缺口已经不大。
### 2. 旧版 0004 中的关键缺口大多已关闭
这次复查确认,以下能力已经不应再视为缺失:
- `beta.threads.create_and_run_stream()`、`create_and_stream()`、`submit_tool_outputs_stream()`、`stream()` 已接入 `AssistantStream`
- `create_and_run_poll()`、`poll()`、`create_and_poll()`、`submit_tool_outputs_and_poll()` 已实现
- `responses.stream_response(response_id)`、`starting_after()`、`final_response()` 已实现
- `ResponseStream` 已能聚合 `response.created`、`response.output_item.added`、`response.content_part.added`、`response.output_text.*`、`response.function_call_arguments.delta`
- `ChatCompletion` 层已有 `LengthFinishReasonError`、`ContentFilterFinishReasonError`
- `ChatCompletionMessage` 已支持解析 assistant content 和 tool arguments
- `run_tools().send_streaming()` 已实现
- `ClientBuilder::from_env()`、`http_client()`、`log_level()`、`logger()` 已实现
- `UploadSource::from_response()` 已实现
- 动态 path segment 已统一走 `encode_path_segment()`
### 3. Realtime / Responses WebSocket 已从“占位”变成“可用”
当前 `openai-rs` 已具备:
- Realtime WebSocket 连接、鉴权、发送、关闭、事件流
- Responses WebSocket 连接、鉴权、发送、关闭、事件流
- Azure Realtime 的 deployment / `api-version` 语义
- Realtime `client_secrets` / `calls`
- Beta Realtime `sessions` / `transcription_sessions`
这部分已经不是“缺功能”,而是“高层封装形态还没和 Node 完全一样”。
## 仍然存在的功能差距
下面这些是当前仍然存在、并且确实影响“是否完全覆盖 `openai-node`”判断的差距。
### P0-1. 运行时形态仍然不是 Node 的 emitter 模型
这是现在最大的剩余差距。
`openai-node` 在 `src/lib/*` 中有一整套事件驱动 runtime:
- `ChatCompletionStream`
- `ChatCompletionRunner`
- `ChatCompletionStreamingRunner`
- `AssistantStream`
- `ResponseStream`
- `EventEmitter`
- `EventStream`
这些 runtime 不只是“能迭代流”,而是提供:
- `.on(...)` 事件监听
- `connect / error / abort / end` 生命周期
- `finalMessage()`、`finalContent()`、`finalFunctionToolCall()`、`totalUsage()` 等高层终态 helper
- `current...Snapshot` / `currentEvent` 语义
- `toReadableStream()` / `fromReadableStream()` 桥接
`openai-rs` 现在已经补上了对应的高层运行时类型:
- `ChatCompletionEventStream` / `ChatCompletionRuntimeEvent`
- `AssistantEventStream` / `AssistantRuntimeEvent`
- `ResponseEventStream` / `ResponseRuntimeEvent`
- `ChatCompletionRunner` / `ChatCompletionStreamingRunner`
- 独立 `OpenAIRealtimeWebSocket` / `OpenAIRealtimeWS`
但它仍然没有做成 `openai-node` 那种 `.on(...)` emitter 形态。剩余差异主要是:
- 事件发射器语义
- 统一的 `connect / error / abort / end` 回调模型
- `ReadableStream` 桥接语义
### P0-2. `ReadableStream` / `fromReadableStream()` / `toReadableStream()` 仍未对齐
`openai-node` 的 Chat / Assistant / Response runtime 都支持:
- `fromReadableStream()`
- `toReadableStream()`
这对 Node/browser 边界和前后端转发很重要。
`openai-rs` 当前统一走 `futures::Stream`,因此能力是有的,但没有等价的桥接 helper。这仍然是和 `openai-node` 的一项真实差距。
### P0-3. 事件类型已经补齐,但还没有 Node 那样的“订阅式”使用方式
当前 `openai-rs` 已经具备:
- `messageCreated`
- `messageDelta`
- `messageDone`
- `runStepCreated`
- `runStepDelta`
- `runStepDone`
- `toolCallCreated`
- `toolCallDelta`
- `toolCallDone`
- `textCreated`
- `textDelta`
- `textDone`
- `imageFileDone`
- `response.output_text.delta`
- `response.function_call_arguments.delta`
这些能力现在都能通过 typed runtime event 读到。
剩余差距不再是“没有这些事件”,而是:
- 事件是通过 `Stream<Item = Event>` 消费
- 不是通过 `.on('event', ...)` 订阅
### P0-4. runner 已补齐,但仍不是持续在线的事件驱动对象
`openai-node` 的 `runTools()` runner 可以持续发射事件;`openai-rs` 现在虽然已经有:
- `ChatCompletionRunner`
- `ChatCompletionStreamingRunner`
- 中间态 message / completion / tool result 收集
- `final_chat_completion()` / `final_message()` / `final_function_tool_call_result()` / `total_usage()`
但它仍然是“执行完后返回 trace”,而不是 Node 那种可边跑边订阅的 runner 对象。
### P0-5. 独立 Realtime 客户端形态仍有差异
`openai-node` 公开了这些独立客户端:
- `OpenAIRealtimeWS`
- `OpenAIRealtimeWebSocket`
- Azure 对应变体
并且是典型的 emitter 形态。
`openai-rs` 现在已经补了:
- `OpenAIRealtimeWebSocket`
- `OpenAIRealtimeWS`
- `OpenAIResponsesWebSocket`
- 原有的 `client.realtime().ws().connect()`
- 原有的 `client.responses().ws().connect()`
也就是说:
- 独立类型已经有了
- endpoint、鉴权、收发消息都已经有了
- 但仍没有 Node 那种 emitter API、browser 保护语义和 Node/browser 双实现结构
其中浏览器相关保护和 `dangerouslyAllowBrowser` 对 Rust SDK 不完全适用,但如果以“是否完整覆盖 `openai-node` 公开 API”来衡量,这仍然是差异点。
### P1-1. 上传 helper 仍然没有 Node 的 `toFile()` 等价层
`openai-node` 的 `toFile()` 支持从多种输入统一构造上传对象:
- `File`
- `Response`
- `Blob`
- `Buffer`
- `Uint8Array`
- `ReadStream`
- `AsyncIterable`
`openai-rs` 现在已经补了:
- `to_file()`
- `ToFileInput`
- `UploadSource::from_path()`
- `UploadSource::from_bytes()`
- `UploadSource::from_reader()`
- `UploadSource::from_response()`
这已经不再是“没有统一 helper”的问题了。当前剩余差距主要是输入面仍比 Node 小:
- 还没有 `Blob` / `AsyncIterable` 等 Node/browser 风格输入
- 更偏 Rust 服务端场景
### P1-2. 路径安全问题已修复,但还没有 Node 那套“显式校验报错语义”
旧版 0004 把 path safety 标成正确性缺口,这个结论现在已经过时了。
当前 `openai-rs` 已经对动态 path segment 做了 percent-encoding,因此“动态 ID 改写 URL 结构”的问题已经解决。
但和 `openai-node/tests/path.test.ts` 比起来,`openai-rs` 还缺:
- 对危险 segment 的显式拒绝语义
- 更详细的诊断错误信息
- 类似 Node path template tag 的系统化校验
所以这一项现在应视为“语义和测试还没完全对齐”,而不是“路径仍然不安全”。
## 与 Node 不同,但不应误判为缺功能的地方
下面这些差异需要单独说明,否则容易把“API 形态不同”误判成“没实现”。
### 1. Rust 用 `ApiResponse<T>` / `send_with_meta()`,不是 `_request_id` 注入
`openai-node` 倾向于:
- 在对象上注入 `_request_id`
- 提供 `withResponse()`
`openai-rs` 当前是:
- `ApiResponse<T>`
- `ResponseMeta`
- `send_with_meta()`
这不是功能缺失,而是公开 API 设计不同。
### 2. Rust 用 `futures::Stream`,不是 `ReadableStream`
`openai-node` 里很多 helper 都围绕 `ReadableStream` 和 emitter 组织。
`openai-rs` 选择的是:
- `futures::Stream`
- 明确的终态 helper
这会导致 API 形态不一样,但不能简单算成“流式能力没实现”。
### 3. 浏览器防护语义并不完全适用于 Rust SDK
`dangerouslyAllowBrowser`、ephemeral browser token consumption 这一类能力在 Node/browser SDK 里很重要,但对 Rust 服务端 SDK 并不是同一个问题域。
这类差异应当单独标注为“目标运行时不同”,而不是一律当成缺失。
## tests 覆盖差距
### 1. 测试广度仍明显落后于 `openai-node`
按 `tests/` 目录文件数静态统计:
- `openai-node/tests`: 74 个 `.ts` 文件
- `openai-rs/tests`: 9 个 `.rs` 文件
`openai-rs` 还在 `src/providers/mod.rs`、`src/stream/mod.rs`、`src/webhooks/mod.rs` 有少量模块内单测,但整体测试广度仍明显小于 `openai-node`。
### 2. 资源级 contract tests 还不够细
`openai-node` 基本是按资源拆开的:
- `containers`
- `conversations`
- `evals`
- `realtime/client-secrets`
- `realtime/calls`
- `skills`
- `videos`
- `beta/chatkit`
- `beta/threads/runs/steps`
- 等等
`openai-rs` 当前 contract tests 仍然集中在少数文件里,覆盖重点主要是:
- chat
- responses
- vector stores
- 路径编码
- logger/env
- websocket happy path
这意味着:
- 主链路有验证
- 长尾资源的回归保障还不够细
### 3. 仍然缺少的测试主题
当前最值得补的测试主题有:
- `ChatCompletionStream` 的高层终态 helper 行为测试
- `AssistantStream` 的事件拆分 / 快照演化测试
- `ResponseStream` 的 typed event / reasoning / function-call 聚合测试
- `run_tools()` 的多轮工具调用与错误路径测试
- Realtime / Responses WebSocket 的错误、关闭、异常消息测试
- `UploadSource` 的文件名推导、错误输入、reader/response 分支测试
- logger 的敏感头脱敏、输入不被 mutate 的测试
- `send_with_meta()` / `ResponseMeta.request_id` 的显式测试
- path safety 的更多边界条件测试
- 长尾资源的逐资源 contract tests
### 4. 当前已经有但仍显偏薄的测试面
当前 `openai-rs` 已经覆盖了一些关键主题:
- Chat request body snapshot
- API error mapping snapshot
- Responses stream aggregation snapshot
- Realtime / Responses WebSocket 事件解码 snapshot
- env / custom http client / logger
- response continuation
- beta run poll helper
- 路径编码
- websocket happy path
这些测试足以证明“核心链路已可用”,但还不足以证明“已经达到 `openai-node` 同等级别的回归密度”。
## 推荐后续补齐顺序
如果目标是继续逼近 `openai-node` 的完整公开能力,我建议按这个顺序补:
1. 给 `ChatCompletionStream`、`AssistantStream`、`ResponseStream` 增加 typed event / runner 层
2. 补一个更接近 Node 语义的 `ChatCompletionRunner` / `ChatCompletionStreamingRunner`
3. 补独立 Realtime 客户端形态,统一 `RealtimeSocket` / `ResponsesSocket` 的高层 API
4. 增强上传 helper,补 `toFile()` 风格的人体工学接口
5. 补 logger、meta、path safety、upload 的专项测试
6. 按资源拆分 contract tests,把长尾资源逐步拉到与 Node 相近的覆盖密度
## 最终判断
最终判断如下:
- `openai-rs` 已经不再是“只有底层 HTTP 路径”的阶段
- 旧版 0004 中列出的多项 P0 缺口已经关闭
- 当前最大的差距,已经从“功能有没有”转移到“高层运行时语义和测试广度有没有完全对齐”
所以今天的结论应更新为:
- `openai-rs` 已经具备高覆盖度的 OpenAI Rust SDK 能力
- 但还没有完全复刻 `openai-node` 的高层 runtime 形态和测试矩阵
- 要说“完全覆盖 `openai-node`”,现在仍然差最后一段以事件 runtime 和测试广度为主的工作