Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 98 additions & 97 deletions GUIDE.md

Large diffs are not rendered by default.

130 changes: 66 additions & 64 deletions OVERVIEW.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
# 钉钉 Channel SDK 家族 · 项目总览
**English** | [简体中文](./OVERVIEW.zh-CN.md)

> 起点议题:[DingTalk-Real-AI/dingtalk-workspace-cli#796](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/issues/796)——「钉钉未来会出 Channel 这样的集成 SDK 吗」。
> 本项目给出答案:**四个语言、效果对齐、即拿即用**。
# DingTalk Channel SDK Family · Project Overview

## 1. 产出物
> Origin issue: [DingTalk-Real-AI/dingtalk-workspace-cli#796](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/issues/796)—"Will DingTalk offer a Channel-like integration SDK in the future?"
> This project provides the answer: **Four languages, effect parity, ready to use**.

| 仓库 | 语言 | 依赖 | 测试 |
## 1. Deliverables

| Repository | Language | Dependencies | Tests |
|---|---|---|---|
| [typefield/dingtalk-channel-sdk-go](https://github.com/typefield/dingtalk-channel-sdk-go) | Go 1.22+ | gorilla/websocket | 19 tests(-race 干净) |
| [typefield/dingtalk-channel-sdk-nodejs](https://github.com/typefield/dingtalk-channel-sdk-nodejs) | Node 18+ | ws | 12 tests |
| [typefield/dingtalk-channel-sdk-python](https://github.com/typefield/dingtalk-channel-sdk-python) | Python 3.10+ | websockets | 12 tests |
| [typefield/dingtalk-channel-sdk-java](https://github.com/typefield/dingtalk-channel-sdk-java) | JDK 8+ | Java-WebSocket + Gson | 12 tests |
| [DingTalk-Real-AI/dingtalk-channel-sdk-go](https://github.com/DingTalk-Real-AI/dingtalk-channel-sdk-go) | Go 1.22+ | gorilla/websocket | 19 tests (-race clean) |
| [DingTalk-Real-AI/dingtalk-channel-sdk-nodejs](https://github.com/DingTalk-Real-AI/dingtalk-channel-sdk-nodejs) | Node 18+ | ws | 12 tests |
| [DingTalk-Real-AI/dingtalk-channel-sdk-python](https://github.com/DingTalk-Real-AI/dingtalk-channel-sdk-python) | Python 3.10+ | websockets | 12 tests |
| [DingTalk-Real-AI/dingtalk-channel-sdk-java](https://github.com/DingTalk-Real-AI/dingtalk-channel-sdk-java) | JDK 8+ | Java-WebSocket + Gson | 12 tests |

每仓含:完整源码、单测、`SPEC.md`(四语言统一契约)、流式 echo 示例、**livecheck 真实联调程序**、README、MIT LICENSE。
Each repository contains: complete source code, unit tests, `SPEC.md` (unified contract across four languages), streaming echo examples, **livecheck real integration program**, README, MIT LICENSE.

## 2. 定位与边界
## 2. Positioning and Boundaries

**与 Agent runtime 解耦的会话接入层**。SDK 负责"通道"的全部脏活,开发者只写「用户说了什么、机器人回什么」:
**A conversation access layer decoupled from Agent runtime**. The SDK handles all the dirty work of the "channel", developers only write "what the user said and what the bot should respond with":

- **负责**:Stream 长连接(建连/心跳/指数退避重连/服务端 disconnect 自愈)、事件双层去重 + 过期消息过滤、sessionWebhook 回复(文本/Markdown/图片,超长自动分片)、AI 卡片流式输出(打字机、帧间隔防竞态、看门狗孤儿保护)、卡片 API 全局限流与 QpsLimit 退避、媒体上传/下载与媒体消息(file/video/audio)、Markdown 归一化、主动发消息(单聊/群聊 + @)、🤔Thinking/🥳Done 状态章、显式中止 Abort、错误兜底冷却
- **不管**:Agent runtime(模型/prompt/工具编排)、会话上下文持久化、凭据存储、业务操作(文档/表格/日历——dws CLI 与 skills 的领域)
- **Responsible for**: Stream long connection (connect/heartbeat/exponential backoff reconnection/server disconnect recovery), dual-layer event deduplication + expired message filtering, sessionWebhook replies (text/Markdown/image, auto-chunking for oversized content), AI card streaming output (typewriter effect, inter-frame interval race protection, watchdog orphan protection), card API global rate limiting and QpsLimit backoff, media upload/download and media messages (file/video/audio), Markdown normalization, proactive messaging (DM/group + @), 🤔Thinking/🥳Done status badges, explicit abort, error fallback cooldown
- **Not responsible for**: Agent runtime (model/prompt/tool orchestration), conversation context persistence, credential storage, business operations (docs/spreadsheets/calendar—domain of dws CLI and skills)

## 3. 快速上手(四语言同构)
## 3. Quick Start (Four Languages Isomorphic)

```go
ch := channel.New(channel.Config{ClientID: "ding...", ClientSecret: "..."})
ch.OnMessage(func(ctx context.Context, msg *channel.IncomingMessage, reply channel.Reply) error {
s, _ := reply.Stream(ctx) // 秒级出"输入中"卡片
s, _ := reply.Stream(ctx) // "Typing" card appears in seconds
for _, tok := range myLLM(msg.Text) {
_ = s.Append(tok) // 打字机追加(800ms 节流+trailing flush)
_ = s.Append(tok) // Typewriter append (800ms throttle + trailing flush)
}
return s.Finish("") // 终帧定格
return s.Finish("") // Final frame finalization
})
ch.Start(ctx)
```
Expand All @@ -46,64 +48,64 @@ async def handle(msg, reply): s = await reply.stream(); await s.append(tok); awa
ch.onMessage((msg, reply) -> { CardStreamer s = reply.stream(); s.append(tok); s.finish(""); });
```

非流式:`reply.Text/Markdown/Image`、附件下载 `reply.DownloadURL`、媒体上传 `reply.UploadMedia`;
主动发消息(不依赖入站):`ch.SendText/SendMarkdown/SendImage`,群发支持 `AtUserIds/AtAll`;
卡片交互:`ch.OnCardAction`(注册即自动订阅 card topic)。
Non-streaming: `reply.Text/Markdown/Image`, attachment download `reply.DownloadURL`, media upload `reply.UploadMedia`;
Proactive messaging (independent of inbound): `ch.SendText/SendMarkdown/SendImage`, group messaging supports `AtUserIds/AtAll`;
Card interaction: `ch.OnCardAction` (registration automatically subscribes to card topic).

## 4. 效果对齐(验收清单 E1–E10,见 SPEC §0)
## 4. Effect Parity (Acceptance Checklist E1–E10, see SPEC §0)

| | 用户可见效果 | 实现 |
| | User-Visible Effect | Implementation |
|---|---|---|
| E1 | 发消息秒级出"输入中"卡片 | `stream()` 立即建卡+投递 INPUTING |
| E2 | 打字机平滑追加 | streaming 接口 + 800ms 节流 + **trailing flush**(窗口内不丢)+ 长间隔 300ms 攒批 |
| E3 | 完成后 loading 消失、Markdown 定格 | isFinalize 终帧 + FINISHED 状态 |
| E4 | 卡片失败/限流用户无感 | 静默降级 webhook 文本;QpsLimit 退避 2s 重试 |
| E5 | 群聊/单聊同一体验 | 同一 Reply API;投递目标自动选;群聊剥 @ 前缀 |
| E6 | 绝不重复回复 | messageId+msgId 双层去重(TTL 5min) |
| E7 | 卡片交互闭环 | OnCardAction + 自动订阅 |
| E8 | 永不掉线 | 指数退避重连 + disconnect 即时重连 + 120s/5s 心跳 + ACK 先行 |
| E9 | 媒体收发 | uploadMedia(OAPI multipart)/ downloadURL / image 回复 |
| E10 | Markdown 渲染质量 | normalizeForCard(代码块/表格/引用钉钉渲染规则) |

每条在四语言均有对应单测;E8 另有专门的断线重连 e2e 回归。

## 5. 协议保真(真源,非文档臆测)

| 能力 | 真源 |
| E1 | "Typing" card appears seconds after sending message | `stream()` immediately creates card + delivers INPUTING |
| E2 | Smooth typewriter append | streaming interface + 800ms throttle + **trailing flush** (no drops within window) + long interval 300ms batching |
| E3 | Loading disappears after completion, Markdown finalizes | isFinalize final frame + FINISHED status |
| E4 | Card failure/rate limiting transparent to user | Silent fallback to webhook text; QpsLimit backoff 2s retry |
| E5 | Same experience in group/DM | Same Reply API; delivery target auto-selected; group strips @ prefix |
| E6 | Never duplicate replies | messageId+msgId dual-layer deduplication (TTL 5min) |
| E7 | Card interaction closed loop | OnCardAction + auto-subscribe |
| E8 | Never disconnects | Exponential backoff reconnection + disconnect immediate reconnection + 120s/5s heartbeat + ACK first |
| E9 | Media send/receive | uploadMedia (OAPI multipart) / downloadURL / image reply |
| E10 | Markdown rendering quality | normalizeForCard (code blocks/tables/quotes DingTalk rendering rules) |

Each item has corresponding unit tests in all four languages; E8 has dedicated disconnect reconnection e2e regression.

## 5. Protocol Fidelity (Ground Truth, Not Documentation Speculation)

| Capability | Ground Truth Source |
|---|---|
| Stream 线协议(open/wss/帧/ACK/心跳/topic 常量) | 官方 dingtalk-stream-sdk-go 源码逐行对照 |
| AI 卡片五步协议 + 限流 + Markdown 归一化 | 官方 connector(dingtalk-openclaw-connector)card.ts |
| token(新版/OAPI 双轨)与 sessionWebhook 载荷 | 官方 connector token.ts / messaging.ts + 官方文档校验 |
| 主动发消息 API | dws(dingtalk-workspace-cli)源码 |
| ticket 编码 / localIp / UA 头 | 四门官方 stream SDK 交叉对照取齐 |
| Stream wire protocol (open/wss/frames/ACK/heartbeat/topic constants) | Official dingtalk-stream-sdk-go source code line-by-line comparison |
| AI card five-step protocol + rate limiting + Markdown normalization | Official connector (dingtalk-openclaw-connector) card.ts |
| Token (new version/OAPI dual-track) and sessionWebhook payload | Official connector token.ts / messaging.ts + official documentation validation |
| Proactive messaging API | dws (dingtalk-workspace-cli) source code |
| Ticket encoding / localIp / UA headers | Cross-referenced across all four official stream SDKs |

Review 轮次中修复的协议级问题:msgParam 字符串化 JSON(官方文档要求)、Go 版 disconnect 误停、ACK 先行语义、ticket URL 编码。
Protocol-level issues fixed during review rounds: msgParam JSON stringification (official documentation requirement), Go disconnect mis-stop, ACK first semantics, ticket URL encoding.

## 6. 关键架构决策:为什么自研传输层而非引用官方 stream-sdk
## 6. Key Architectural Decision: Why Build Custom Transport Layer Instead of Using Official stream-sdk

1. **官方 connector 自己都不信任**:钉钉官方 connector 源码 `autoReconnect:false, keepAlive:false` 全关重写(issue #571/#536/#573)
2. **四语言一致是本项目验收标准**,而官方四门 SDK 心跳/重连/编码行为互不一致,引用即继承分歧
3. **依赖重量**:官方 Java 版引 Netty 多模块、Python 版带 requests+aiohttp 双 HTTP 栈;自研每语言仅 1–2 个小依赖,传输层约 300 行/语言
4. 留有演进接缝(`Channel → StreamConn → onFrame` 单一边界),上游 SDK 成熟后可加官方适配后端
1. **Official connector doesn't trust itself**: DingTalk official connector source code has `autoReconnect:false, keepAlive:false` all disabled and rewritten (issues #571/#536/#573)
2. **Four-language consistency is this project's acceptance standard**, while official four SDKs have inconsistent heartbeat/reconnection/encoding behaviors; referencing them inherits divergence
3. **Dependency weight**: Official Java version pulls Netty multi-modules, Python version includes requests+aiohttp dual HTTP stack; custom implementation only 1–2 small dependencies per language, transport layer ~300 lines/language
4. Leaves evolution seam (`Channel → StreamConn → onFrame` single boundary), official SDK adapter backend can be added later when upstream matures

## 7. 质量证据
## 7. Quality Evidence

- **测试**:Go 14 / Node 12 / Python 12 / Java 12(BUILD SUCCESS),全部含 e2e(假网关+假 API)与断线重连回归
- **真实联调**:每语言 `example/livecheck` 一键验证(连接→收消息→文本回复→卡片流式全周期→媒体上传,逐步 PASS/FAIL):
`DD_CLIENT_ID=... DD_CLIENT_SECRET=... go run ./example/livecheck`(Node `npm run live`;Python `python example/livecheck.py`;Java `mvn exec:java`)
- **Code review**:三轮(协议一致性 / 效果对齐 / stream 层对官方 SDK),修复 8 处问题,全部有回归测试
- **Tests**: Go 14 / Node 12 / Python 12 / Java 12 (BUILD SUCCESS), all include e2e (mock gateway + mock API) and disconnect reconnection regression
- **Real integration**: Each language's `example/livecheck` one-command verification (connection→receive message→text reply→card streaming full cycle→media upload, progressive PASS/FAIL):
`DD_CLIENT_ID=... DD_CLIENT_SECRET=... go run ./example/livecheck` (Node `npm run live`; Python `python example/livecheck.py`; Java `mvn exec:java`)
- **Code review**: Three rounds (protocol consistency / effect parity / stream layer vs official SDK), fixed 8 issues, all have regression tests

## 8. 已知边界与路线图
## 8. Known Boundaries and Roadmap

- 真实凭据联跑待执行(livecheck 就绪,一条命令)
- >20MB 文件分块上传(v0.2)
- 卡片模板默认值为 connector 内置模板,对外发布建议配置化强调
- 平台差异项(reaction/评论/转发)随钉钉开放平台演进跟进
- 可选:官方 stream-sdk 适配后端(`WithTransport` 接缝已留)
- Real credential live testing to be executed (livecheck ready, one command)
- >20MB file chunked upload (v0.2)
- Card template default value is connector built-in template, recommended to make it configurable for public release
- Platform differences (reaction/comment/forward) will follow DingTalk Open Platform evolution
- Optional: Official stream-sdk adapter backend (seam already left via `WithTransport`)

## 9. 前置条件
## 9. Prerequisites

钉钉开发者后台创建**企业内部应用**并开启机器人,取得 ClientID/ClientSecret。Stream 模式无需公网 IP 与域名。
Create **Enterprise Internal Application** in DingTalk Developer Console and enable bot, obtain ClientID/ClientSecret. Stream mode requires no public IP or domain.

---
License:MIT | 契约:各仓 `SPEC.md` | 版本:v0.1.0(2026-08)
License: MIT | Contract: `SPEC.md` in each repo | Version: v0.1.0 (Aug 2026)
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Requires JDK 8+. Dependencies: Java-WebSocket and Gson only.

```xml
<dependency>
<groupId>io.github.typefield</groupId>
<groupId>com.dingtalk</groupId>
<artifactId>dingtalk-channel-sdk</artifactId>
<version>0.1.0</version>
</dependency>
Expand Down Expand Up @@ -69,7 +69,7 @@ s.finish("");
Application code typically imports only the root package:

```java
import io.github.typefield.dingtalk.channel.DingTalkChannel;
import com.dingtalk.channel.DingTalkChannel;
```

Sub-packages (`normalize` / `safety` / `outbound`) are internal layering and carry no compatibility promise.
Expand Down Expand Up @@ -98,7 +98,7 @@ Live check:

```bash
DD_CLIENT_ID=... DD_CLIENT_SECRET=... \
mvn -q compile exec:java -Dexec.mainClass=io.github.typefield.dingtalk.channel.example.LiveCheck
mvn -q compile exec:java -Dexec.mainClass=com.dingtalk.channel.example.LiveCheck
```

## License
Expand Down
6 changes: 3 additions & 3 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

```xml
<dependency>
<groupId>io.github.typefield</groupId>
<groupId>com.dingtalk</groupId>
<artifactId>dingtalk-channel-sdk</artifactId>
<version>0.1.0</version>
</dependency>
Expand Down Expand Up @@ -69,7 +69,7 @@ s.finish("");
业务代码通常只需导入根包:

```java
import io.github.typefield.dingtalk.channel.DingTalkChannel;
import com.dingtalk.channel.DingTalkChannel;
```

子包(`normalize` / `safety` / `outbound`)属内部分层,不在兼容性承诺范围内。
Expand Down Expand Up @@ -98,7 +98,7 @@ mvn test # 34 个测试

```bash
DD_CLIENT_ID=... DD_CLIENT_SECRET=... \
mvn -q compile exec:java -Dexec.mainClass=io.github.typefield.dingtalk.channel.example.LiveCheck
mvn -q compile exec:java -Dexec.mainClass=com.dingtalk.channel.example.LiveCheck
```

## License
Expand Down
Loading