channel-bot-cli-antigravity 0.1.0__tar.gz

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 (109) hide show
  1. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/SKILL.md +510 -0
  2. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/go/a2ui/main.go +50 -0
  3. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/go/a2ui-card.json +41 -0
  4. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/go/a2ui-update.json +10 -0
  5. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/go/agent/main.go +300 -0
  6. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/go/echo/main.go +51 -0
  7. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/go/fullflow/main.go +115 -0
  8. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/go/livecheck/main.go +125 -0
  9. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/java/A2UI.java +39 -0
  10. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/java/EchoBot.java +45 -0
  11. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/java/FullFlow.java +101 -0
  12. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/java/LiveCheck.java +100 -0
  13. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/java/a2ui-card.json +41 -0
  14. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/java/a2ui-update.json +10 -0
  15. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/nodejs/a2ui-card.json +41 -0
  16. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/nodejs/a2ui-update.json +10 -0
  17. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/nodejs/a2ui.mjs +14 -0
  18. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/nodejs/echo.mjs +37 -0
  19. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/nodejs/fullflow.mjs +76 -0
  20. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/nodejs/livecheck.mjs +85 -0
  21. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/python/a2ui-card.json +41 -0
  22. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/python/a2ui-update.json +10 -0
  23. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/python/a2ui.py +25 -0
  24. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/python/echo.py +50 -0
  25. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/python/fullflow.py +97 -0
  26. channel_bot_cli_antigravity-0.1.0/.agents/skills/dingtalk-channel-sdk-docs/example/python/livecheck.py +94 -0
  27. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/SKILL.md +147 -0
  28. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/agent_skills.md +38 -0
  29. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/app_data_dir_override.md +29 -0
  30. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/budget_limits.md +64 -0
  31. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/cancellation.md +31 -0
  32. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/compaction.md +43 -0
  33. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/custom_tool.md +117 -0
  34. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/customizing_retries.md +50 -0
  35. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/hello_world.md +80 -0
  36. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/hooks.md +181 -0
  37. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/local_models.md +191 -0
  38. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/mcp_tools.md +147 -0
  39. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/multimodal.md +63 -0
  40. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/periodic_trigger.md +67 -0
  41. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/persistence.md +42 -0
  42. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/persona_config.md +77 -0
  43. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/structured_output.md +48 -0
  44. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/subagents.md +105 -0
  45. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/web_tools.md +61 -0
  46. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/examples/getting_started/workflows.md +138 -0
  47. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/references/agent_configuration.md +375 -0
  48. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/references/architecture.md +77 -0
  49. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/references/built_in_tools.md +60 -0
  50. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/references/error_handling.md +85 -0
  51. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/references/local_models.md +225 -0
  52. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/references/mcp_integration.md +196 -0
  53. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/references/observability.md +85 -0
  54. channel_bot_cli_antigravity-0.1.0/.agents/skills/google-antigravity-sdk/references/safety_policies.md +244 -0
  55. channel_bot_cli_antigravity-0.1.0/.agents/skills/lark-channel-sdk-python-docs/SKILL.md +232 -0
  56. channel_bot_cli_antigravity-0.1.0/.agents/skills/lark-channel-sdk-python-docs/docs/cardkit-streaming.md +146 -0
  57. channel_bot_cli_antigravity-0.1.0/.agents/skills/lark-channel-sdk-python-docs/docs/dedup-architecture.md +205 -0
  58. channel_bot_cli_antigravity-0.1.0/.agents/skills/lark-channel-sdk-python-docs/docs/markdown.md +118 -0
  59. channel_bot_cli_antigravity-0.1.0/.agents/skills/lark-channel-sdk-python-docs/docs/meeting-channel.md +257 -0
  60. channel_bot_cli_antigravity-0.1.0/.agents/skills/lark-channel-sdk-python-docs/docs/migration-from-lark-oapi.md +163 -0
  61. channel_bot_cli_antigravity-0.1.0/.agents/skills/lark-channel-sdk-python-docs/docs/quickstart.md +203 -0
  62. channel_bot_cli_antigravity-0.1.0/.agents/skills/lark-channel-sdk-python-docs/docs/reference.md +483 -0
  63. channel_bot_cli_antigravity-0.1.0/.agents/skills/lark-channel-sdk-python-docs/docs/release-notes/v1.0.0.md +48 -0
  64. channel_bot_cli_antigravity-0.1.0/.agents/skills/lark-channel-sdk-python-docs/docs/release-notes/v1.1.0.md +14 -0
  65. channel_bot_cli_antigravity-0.1.0/.agents/skills/lark-channel-sdk-python-docs/docs/security.md +178 -0
  66. channel_bot_cli_antigravity-0.1.0/.agents/skills/lark-channel-sdk-python-docs/docs/webhook-server.md +189 -0
  67. channel_bot_cli_antigravity-0.1.0/.agents/skills/lark-channel-sdk-python-docs/samples/channel/echo_bot.py +35 -0
  68. channel_bot_cli_antigravity-0.1.0/.agents/skills/lark-channel-sdk-python-docs/samples/channel/meeting_follow_agenda.py +76 -0
  69. channel_bot_cli_antigravity-0.1.0/.agents/skills/lark-channel-sdk-python-docs/samples/channel/meeting_join_bot.py +82 -0
  70. channel_bot_cli_antigravity-0.1.0/.github/workflows/publish.yml +35 -0
  71. channel_bot_cli_antigravity-0.1.0/.gitignore +15 -0
  72. channel_bot_cli_antigravity-0.1.0/.python-version +1 -0
  73. channel_bot_cli_antigravity-0.1.0/AGENTS.md +143 -0
  74. channel_bot_cli_antigravity-0.1.0/LICENSE +21 -0
  75. channel_bot_cli_antigravity-0.1.0/PKG-INFO +472 -0
  76. channel_bot_cli_antigravity-0.1.0/README.md +455 -0
  77. channel_bot_cli_antigravity-0.1.0/pyproject.toml +46 -0
  78. channel_bot_cli_antigravity-0.1.0/skills/channel-bot-cli-antigravity/SKILL.md +319 -0
  79. channel_bot_cli_antigravity-0.1.0/skills-lock.json +11 -0
  80. channel_bot_cli_antigravity-0.1.0/src/channel_bot_cli_antigravity/__init__.py +31 -0
  81. channel_bot_cli_antigravity-0.1.0/src/channel_bot_cli_antigravity/agent.py +413 -0
  82. channel_bot_cli_antigravity-0.1.0/src/channel_bot_cli_antigravity/channel.py +8 -0
  83. channel_bot_cli_antigravity-0.1.0/src/channel_bot_cli_antigravity/channels/__init__.py +82 -0
  84. channel_bot_cli_antigravity-0.1.0/src/channel_bot_cli_antigravity/channels/base.py +166 -0
  85. channel_bot_cli_antigravity-0.1.0/src/channel_bot_cli_antigravity/channels/dingtalk.py +394 -0
  86. channel_bot_cli_antigravity-0.1.0/src/channel_bot_cli_antigravity/channels/feishu.py +428 -0
  87. channel_bot_cli_antigravity-0.1.0/src/channel_bot_cli_antigravity/cli.py +405 -0
  88. channel_bot_cli_antigravity-0.1.0/src/channel_bot_cli_antigravity/config.py +203 -0
  89. channel_bot_cli_antigravity-0.1.0/src/channel_bot_cli_antigravity/py.typed +0 -0
  90. channel_bot_cli_antigravity-0.1.0/src/channel_bot_cli_antigravity/utils.py +152 -0
  91. channel_bot_cli_antigravity-0.1.0/tests/__init__.py +1 -0
  92. channel_bot_cli_antigravity-0.1.0/tests/conftest.py +405 -0
  93. channel_bot_cli_antigravity-0.1.0/tests/test_challenger_m1.py +415 -0
  94. channel_bot_cli_antigravity-0.1.0/tests/test_challenger_m2.py +660 -0
  95. channel_bot_cli_antigravity-0.1.0/tests/test_challenger_m2_config.py +462 -0
  96. channel_bot_cli_antigravity-0.1.0/tests/test_challenger_m3.py +700 -0
  97. channel_bot_cli_antigravity-0.1.0/tests/test_challenger_m3_streaming_media.py +467 -0
  98. channel_bot_cli_antigravity-0.1.0/tests/test_tier1_base_channel.py +198 -0
  99. channel_bot_cli_antigravity-0.1.0/tests/test_tier1_cli.py +115 -0
  100. channel_bot_cli_antigravity-0.1.0/tests/test_tier1_config.py +127 -0
  101. channel_bot_cli_antigravity-0.1.0/tests/test_tier1_dingtalk_channel.py +216 -0
  102. channel_bot_cli_antigravity-0.1.0/tests/test_tier1_feishu_channel.py +201 -0
  103. channel_bot_cli_antigravity-0.1.0/tests/test_tier1_media.py +110 -0
  104. channel_bot_cli_antigravity-0.1.0/tests/test_tier1_normalization.py +134 -0
  105. channel_bot_cli_antigravity-0.1.0/tests/test_tier2_boundaries.py +498 -0
  106. channel_bot_cli_antigravity-0.1.0/tests/test_tier3_combinations.py +307 -0
  107. channel_bot_cli_antigravity-0.1.0/tests/test_tier4_scenarios.py +465 -0
  108. channel_bot_cli_antigravity-0.1.0/tests/test_tier5_concurrency_streaming_stress.py +1011 -0
  109. channel_bot_cli_antigravity-0.1.0/uv.lock +1121 -0
@@ -0,0 +1,510 @@
1
+ ---
2
+ name: dingtalk-channel-sdk-docs
3
+ description: 钉钉 Channel SDK 接入指南与多语言使用手册(Go / Python / Node.js / Java),提供 Stream 长连接、AI 卡片流式打字机、消息与媒体收发、主动通知、卡片交互、安全准入配置及 runnable 示例。
4
+ ---
5
+
6
+ # 钉钉 Channel SDK 接入指南
7
+
8
+ 钉钉 Channel SDK 是专为**大模型 Agent、企业机器人、自动化助手**打造的会话接入层 SDK。它与具体的模型 Runtime(LangChain、LlamaIndex 等)完全解耦,底层自动接管 WebSocket Stream 长连接、心跳重连、消息双层去重、AI 卡片流式输出(打字机)、Markdown 渲染修复、媒体上传与主动通知。
9
+
10
+ 开发者只需要关注一件事:**「用户说了什么,机器人回什么」**。
11
+
12
+ ---
13
+
14
+ ## 目录
15
+
16
+ - [1. 接入效果与核心能力](#1-接入效果与核心能力)
17
+ - [2. 前置准备(一次性)](#2-前置准备一次性)
18
+ - [3. 多语言 SDK 安装](#3-多语言-sdk-安装)
19
+ - [4. 快速开始(四语言快速上手)](#4-快速开始四语言快速上手)
20
+ - [5. 核心使用场景与范式](#5-核心使用场景与范式)
21
+ - [5.1 AI 卡片流式回复(打字机)](#51-ai-卡片流式回复打字机)
22
+ - [5.2 真实大模型(LLM)流式对接范式](#52-真实大模型llm流式对接范式)
23
+ - [5.3 消息表情状态章(🤔思考中 / 🥳完成)](#53-消息表情状态章思考中--完成)
24
+ - [5.4 基础回复(文本 / Markdown / 图片 / 媒体)](#54-基础回复文本--markdown--图片--媒体)
25
+ - [5.5 主动发送消息(单聊 / 群聊 @ / 文件通知)](#55-主动发送消息单聊--群聊---文件通知)
26
+ - [5.6 卡片按钮交互回调(OnCardAction)](#56-卡片按钮交互回调oncardaction)
27
+ - [5.7 安全准入控制与防刷(SafetyPipeline)](#57-安全准入控制与防刷safetypipeline)
28
+ - [5.8 A2UI 动态交互卡片(DWS 接入)](#58-a2ui-动态交互卡片dws-接入)
29
+ - [6. 四语言 API 速查对照](#6-四语言-api-速查对照)
30
+ - [7. 配套使用示例索引(example/)](#7-配套使用示例索引example)
31
+ - [8. 真机一键联调验证(LiveCheck)](#8-真机一键联调验证livecheck)
32
+
33
+ ---
34
+
35
+ ## 1. 接入效果与核心能力
36
+
37
+ | 能力 | 终端用户可见效果 | SDK 内部实现机制 |
38
+ |---|---|---|
39
+ | **秒级卡片响应** | 提问后瞬间出现「输入中...」卡片骨架 | `reply.stream()` 立即创建并投递卡片,不阻塞等待模型首个 Token |
40
+ | **平滑流式打字机** | 回答逐字/逐词平滑浮现,无闪烁跳行 | 内置 800ms 智能节流 + 尾帧补刷 (Trailing Flush) + 去除未结束换行 |
41
+ | **Markdown 定格** | 生成完毕后 loading 消失,定格为高亮 Markdown | 自动发送终帧并收口,适配钉钉代码高亮、表格对齐与换行语法 |
42
+ | **消息防重与高可用** | 网络重试或多实例时绝不重复回复 | 协议层 `messageId` + 业务层 `msgId` 双层去重(TTL 5min) |
43
+ | **自动断线自愈** | 服务端发布、切换或网络断开无感知 | 指数退避重连(1s~30s 带抖动)+ 120s 心跳保活与 5s Pong 超时判死 |
44
+ | **失败静默降级** | 卡片模板无权限或 QPS 限流时用户无感 | 自动退避 2s 重试;卡片投递失败自动降级为 Webhook 纯文本,不报错崩溃 |
45
+
46
+ ---
47
+
48
+ ## 2. 前置准备(一次性)
49
+
50
+ 1. 打开 [钉钉开发者后台](https://open-dev.dingtalk.com),进入或创建**企业内部应用**。
51
+ 2. 在应用能力中添加**机器人**能力,填写基本信息并发布。
52
+ 3. 获取凭据:在「凭证与基础信息」中记录 **ClientID(AppKey)** 和 **ClientSecret(AppSecret)**。
53
+ 4. **无需公网 IP、无需域名、无需配置 Webhook 回调 URL**(采用 Stream 模式,本地开发或内网服务器即可直接双向通讯)。
54
+
55
+ ---
56
+
57
+ ## 3. 多语言 SDK 安装
58
+
59
+ | 语言 | 仓库地址 | 安装命令 |
60
+ |---|---|---|
61
+ | **Python** | [dingtalk-channel-sdk-python](https://github.com/DingTalk-Real-AI/dingtalk-channel-sdk-python) | `pip install dingtalk-channel-sdk` |
62
+ | **Node.js** | [dingtalk-channel-sdk-nodejs](https://github.com/DingTalk-Real-AI/dingtalk-channel-sdk-nodejs) | `npm install dingtalk-channel-sdk` |
63
+ | **Go** | [dingtalk-channel-sdk-go](https://github.com/DingTalk-Real-AI/dingtalk-channel-sdk-go) | `go get github.com/DingTalk-Real-AI/dingtalk-channel-sdk-go` |
64
+ | **Java** | [dingtalk-channel-sdk-java](https://github.com/DingTalk-Real-AI/dingtalk-channel-sdk-java) | Maven: `com.dingtalk:dingtalk-channel-sdk` |
65
+
66
+ ---
67
+
68
+ ## 4. 快速开始(四语言快速上手)
69
+
70
+ 以下四门语言实现完全相同的逻辑:**初始化 → 监听消息 → AI 卡片打字机回复 → 监听卡片按钮点击 → 启动长连接**。
71
+
72
+ ### Python
73
+ ```python
74
+ import asyncio
75
+ import os
76
+ from dingtalk_channel_sdk import DingTalkChannel
77
+
78
+ ch = DingTalkChannel(
79
+ client_id=os.environ["DD_CLIENT_ID"],
80
+ client_secret=os.environ["DD_CLIENT_SECRET"],
81
+ )
82
+
83
+ @ch.on_message
84
+ async def handle_message(msg, reply):
85
+ if not msg.text:
86
+ return # 富媒体在 msg.content / msg.msg_type 中
87
+
88
+ # 1. 秒级弹出 "输入中" 卡片
89
+ s = await reply.stream()
90
+
91
+ # 2. 模拟/真实 LLM 流式输出
92
+ answer = f"**收到你的提问**:{msg.text}\n\n- 发送人:{msg.sender_nick}"
93
+ for token in answer:
94
+ await s.append(token)
95
+ await asyncio.sleep(0.015)
96
+
97
+ # 3. 定格为最终 Markdown
98
+ await s.finish(answer)
99
+
100
+ @ch.on_card_action
101
+ async def handle_card(action, reply):
102
+ await reply.text(f"收到按钮操作: {action.data_content}")
103
+
104
+ if __name__ == "__main__":
105
+ asyncio.run(ch.start()) # 阻塞运行,断线自愈
106
+ ```
107
+
108
+ ### Node.js
109
+ ```javascript
110
+ import { DingTalkChannel } from 'dingtalk-channel-sdk';
111
+
112
+ const ch = new DingTalkChannel({
113
+ clientId: process.env.DD_CLIENT_ID,
114
+ clientSecret: process.env.DD_CLIENT_SECRET,
115
+ });
116
+
117
+ ch.on('message', async (msg, reply) => {
118
+ if (!msg.text) return;
119
+
120
+ const s = await reply.stream(); // 立即出卡
121
+ const answer = `**收到**:${msg.text}\n\n- 发送人:${msg.senderNick}`;
122
+
123
+ for (const char of answer) {
124
+ await s.append(char);
125
+ await new Promise((r) => setTimeout(r, 15));
126
+ }
127
+ await s.finish(answer); // 定格 Markdown
128
+ });
129
+
130
+ ch.on('cardAction', async (action, reply) => {
131
+ await reply.text(`收到按钮点击: ${JSON.stringify(action.dataContent)}`);
132
+ });
133
+
134
+ const controller = new AbortController();
135
+ process.on('SIGINT', () => controller.abort());
136
+ await ch.start(controller.signal);
137
+ ```
138
+
139
+ ### Go
140
+ ```go
141
+ package main
142
+
143
+ import (
144
+ "context"
145
+ "fmt"
146
+ "log"
147
+ "os"
148
+ "time"
149
+
150
+ channel "github.com/DingTalk-Real-AI/dingtalk-channel-sdk-go"
151
+ )
152
+
153
+ func main() {
154
+ ch := channel.New(channel.Config{
155
+ ClientID: os.Getenv("DD_CLIENT_ID"),
156
+ ClientSecret: os.Getenv("DD_CLIENT_SECRET"),
157
+ })
158
+
159
+ ch.OnMessage(func(ctx context.Context, msg *channel.IncomingMessage, reply channel.Reply) error {
160
+ if msg.Text == "" {
161
+ return nil
162
+ }
163
+ s, err := reply.Stream(ctx) // ① 立即出"输入中"卡片
164
+ if err != nil {
165
+ return reply.Text(ctx, "系统繁忙,稍后重试")
166
+ }
167
+
168
+ answer := fmt.Sprintf("**收到**:%s\n\n- 发送人: %s", msg.Text, msg.SenderNick)
169
+ for _, r := range answer { // ② 流式追加
170
+ _ = s.Append(string(r))
171
+ time.Sleep(15 * time.Millisecond)
172
+ }
173
+ return s.Finish(answer) // ③ 终帧定格 Markdown
174
+ })
175
+
176
+ ch.OnCardAction(func(ctx context.Context, action *channel.CardAction, reply channel.Reply) error {
177
+ return reply.Text(ctx, "收到点击: "+string(action.DataContent))
178
+ })
179
+
180
+ log.Fatal(ch.Start(context.Background())) // 阻塞运行,断线自愈
181
+ }
182
+ ```
183
+
184
+ ### Java
185
+ ```java
186
+ package com.example;
187
+
188
+ import com.dingtalk.channel.CardStreamer;
189
+ import com.dingtalk.channel.Config;
190
+ import com.dingtalk.channel.DingTalkChannel;
191
+
192
+ public class App {
193
+ public static void main(String[] args) throws Exception {
194
+ DingTalkChannel ch = DingTalkChannel.create(Config.builder(
195
+ System.getenv("DD_CLIENT_ID"),
196
+ System.getenv("DD_CLIENT_SECRET")
197
+ ).build());
198
+
199
+ ch.onMessage((msg, reply) -> {
200
+ if (msg.text.isEmpty()) return;
201
+
202
+ CardStreamer s = reply.stream(); // 立即出卡
203
+ String answer = "**收到**:" + msg.text + "\n\n- 来自: " + msg.senderNick;
204
+
205
+ for (char c : answer.toCharArray()) {
206
+ s.append(String.valueOf(c));
207
+ Thread.sleep(15);
208
+ }
209
+ s.finish(answer); // 定格 Markdown
210
+ });
211
+
212
+ ch.onCardAction((action, reply) -> {
213
+ reply.text("收到点击: " + action.dataContent);
214
+ });
215
+
216
+ Runtime.getRuntime().addShutdownHook(new Thread(ch::close));
217
+ ch.start(); // 阻塞运行
218
+ }
219
+ }
220
+ ```
221
+
222
+ ---
223
+
224
+ ## 5. 核心使用场景与范式
225
+
226
+ ### 5.1 AI 卡片流式回复(打字机)
227
+ 流式打字机通过 `s = reply.stream()` 初始化,核心方法如下:
228
+ - `s.append(delta)`:向卡片追加文本增量。SDK 会在 800ms 内智能合批,并在结束时通过 Trailing Flush 自动补刷最后一批内容。
229
+ - `s.finish(fullMarkdown)`:定格卡片,关闭 loading 转圈,并以完整 Markdown 渲染。
230
+ - `s.fail("错误提示")`:将卡片置为失败态并呈现提示。
231
+ - `s.abort()`:用户取消生成或外部打断时关闭流。
232
+ - `s.card_delivered` / `s.CardDelivered()`:检查卡片是否在端上投递成功(若因权限未开通,SDK 会自动降级为普通文本回复)。
233
+
234
+ ### 5.2 真实大模型(LLM)流式对接范式
235
+
236
+ 接入 OpenAI 兼容接口(如 DeepSeek、通义千问、Ollama、vLLM)的标准写法:
237
+
238
+ ```python
239
+ from openai import AsyncOpenAI
240
+
241
+ openai_client = AsyncOpenAI(
242
+ api_key=os.environ["OPENAI_API_KEY"],
243
+ base_url=os.environ.get("OPENAI_BASE_URL", "https://api.deepseek.com"),
244
+ )
245
+
246
+ @ch.on_message
247
+ async def handle_agent(msg, reply):
248
+ if not msg.text:
249
+ return
250
+
251
+ # 1. 弹出卡片
252
+ s = await reply.stream()
253
+
254
+ # 2. 调用大模型并流式消费
255
+ response = await openai_client.chat.completions.create(
256
+ model=os.environ.get("AGENT_MODEL", "deepseek-chat"),
257
+ messages=[{"role": "user", "content": msg.text}],
258
+ stream=True,
259
+ )
260
+
261
+ full_chunks = []
262
+ async for chunk in response:
263
+ delta = chunk.choices[0].delta.content or ""
264
+ if delta:
265
+ full_chunks.append(delta)
266
+ await s.append(delta)
267
+
268
+ # 3. 终帧收口
269
+ await s.finish("".join(full_chunks))
270
+ ```
271
+
272
+ > **Go 开发者提示**:在 `example/go/agent/main.go` 中内置了支持 DeepSeek(OpenAI 兼容协议)与 Anthropic 协议的完整 Agent 示例,支持多轮上下文会话缓存与自动降级。
273
+
274
+ ### 5.3 消息表情状态章(🤔思考中 / 🥳完成)
275
+
276
+ 在用户提问的消息气泡上直接打上 Text Emotion 反应(不依赖卡片,用于表示「正在思考」与「已完成」):
277
+
278
+ ```go
279
+ // Go 示例
280
+ ch.OnMessage(func(ctx context.Context, msg *channel.IncomingMessage, reply channel.Reply) error {
281
+ // 收到消息后立即贴上 "🤔思考中"
282
+ _ = ch.MarkThinking(ctx, msg.ConversationID, msg.MsgID)
283
+ defer ch.MarkDone(ctx, msg.ConversationID, msg.MsgID) // 退出时置为 "🥳完成"
284
+
285
+ s, _ := reply.Stream(ctx)
286
+ // ... 执行业务与流式回复 ...
287
+ return s.Finish(answer)
288
+ })
289
+ ```
290
+
291
+ ### 5.4 基础回复(文本 / Markdown / 图片 / 媒体)
292
+
293
+ 在 `on_message` 的 `reply` 句柄中,支持丰富的标准消息形式:
294
+
295
+ ```python
296
+ # 1. 发送纯文本
297
+ await reply.text("你好,这是文本回复")
298
+
299
+ # 2. 发送标准 Markdown
300
+ await reply.markdown("标题", "# 一级标题\n- 列表项\n```python\nprint(1)\n```")
301
+
302
+ # 3. 发送公网图片
303
+ await reply.image("https://example.com/photo.png")
304
+
305
+ # 4. 上传媒体文件(图片/音频/视频/普通文件)并获取 mediaId
306
+ media = await reply.upload_media("image", "avatar.png", img_bytes, "image/png")
307
+ # 上传后的 mediaId 可通过独立媒体消息可靠发送,或提取 media["downloadUrl"]
308
+
309
+ # 5. 下载用户发送在会话中的附件/图片
310
+ file_url = await reply.download_url(resource.download_code, msg.msg_id)
311
+ ```
312
+
313
+ ### 5.5 主动发送消息(单聊 / 群聊 @ / 文件通知)
314
+
315
+ 不依赖入站触发,由系统定时任务、报警监控主动向钉钉推送:
316
+
317
+ ```python
318
+ from dingtalk_channel_sdk import SendTarget
319
+
320
+ # 1. 主动向员工发送单聊文本(需传入员工 staffId)
321
+ await ch.send_text(SendTarget(user_id="staff123"), "【系统提醒】你的审批已通过")
322
+
323
+ # 2. 主动向群聊发送 Markdown 并在群里 @ 特定人员
324
+ await ch.send_markdown(
325
+ SendTarget(
326
+ conversation_id="cid_xxx", # 群的 openConversationId
327
+ at_user_ids=["staff123", "staff456"],# 需要 @ 的员工列表
328
+ ),
329
+ "发布成功通知",
330
+ "@staff123 新版本发布已完成,请核对环境。"
331
+ )
332
+
333
+ # 3. 主动群发 @所有人
334
+ await ch.send_markdown(
335
+ SendTarget(conversation_id="cid_xxx", is_at_all=True),
336
+ "全员周会提醒",
337
+ "@所有人 下午 3:00 周会准时开始。"
338
+ )
339
+
340
+ # 4. 发送独立文件(图片/文档可靠送达)
341
+ await ch.send_file(SendTarget(user_id="staff123"), "report.pdf", raw_media_id)
342
+ ```
343
+
344
+ ### 5.6 卡片按钮交互回调(OnCardAction)
345
+
346
+ 当卡片中包含操作按钮时,用户点击会回调到注册的 `on_card_action` 处理器:
347
+
348
+ ```python
349
+ @ch.on_card_action
350
+ async def on_card(action, reply):
351
+ # action 关键字段:
352
+ # - action.user_id: 点击者的员工 ID
353
+ # - action.out_track_id: 被点击卡片的唯一 ID
354
+ # - action.data_content: 按钮绑定的业务参数 (字典/JSON)
355
+
356
+ print(f"用户 {action.user_id} 触发了按钮: {action.data_content}")
357
+ await reply.text("已收到并处理你的操作请求!")
358
+ ```
359
+
360
+ ### 5.7 安全准入控制与防刷(SafetyPipeline)
361
+
362
+ 面向多人生产群聊,SDK 内置开箱即用的安全过滤管线:
363
+ - **过期消息丢弃**:过滤掉超过 30 分钟的历史消息(防宕机重启刷屏)。
364
+ - **群聊 @机器人 过滤**:只有被 `@` 时才响应,默认忽略 `@所有人`。
365
+ - **单聊/群聊名单限制**:支持设置黑白名单。
366
+ - **连续图片自动合并批处理(MediaBatch)**:连续上传的多张图自动合批为一次处理。
367
+
368
+ ```python
369
+ from dingtalk_channel_sdk import Config, PolicyConfig, MediaBatchConfig
370
+
371
+ cfg = Config(
372
+ client_id=os.environ["DD_CLIENT_ID"],
373
+ client_secret=os.environ["DD_CLIENT_SECRET"],
374
+ # 策略门控
375
+ policy=PolicyConfig(
376
+ require_mention=True, # 群聊必须 @机器人
377
+ respond_to_mention_all=False,# 不响应 @所有人
378
+ dm_mode="open", # 单聊模式:open | allowlist | blocklist | disabled
379
+ admins=["admin_staff_id"], # 管理员名单(豁免所有限制)
380
+ group_allowlist=["cid_auth"],# 仅在指定的群响应
381
+ ),
382
+ # 连续图片自动合并:800ms 内连续发送的多张图片合并为 1 条消息
383
+ media_batch=MediaBatchConfig(enabled=True, delay_s=0.8, max_items=9),
384
+ )
385
+
386
+ ch = DingTalkChannel(config=cfg)
387
+
388
+ # 监听被拦截的无效/未授权事件
389
+ @ch.on_reject
390
+ def handle_reject(event):
391
+ print(f"消息拦截: {event.message_id}, 原因: {event.reason}")
392
+ ```
393
+
394
+ ### 5.8 A2UI 动态交互卡片(DWS 接入)
395
+
396
+ A2UI 是钉钉新一代 AI 原生卡片标准。SDK 支持通过本机的 [DingTalk Workspace CLI (dws)](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli) 发送与增量更新 A2UI 界面:
397
+
398
+ ```python
399
+ import json
400
+ from pathlib import Path
401
+ from dingtalk_channel_sdk import Config, DingTalkChannel, DwsA2UIClient
402
+
403
+ ch = DingTalkChannel(config=Config(
404
+ client_id=os.environ["DD_CLIENT_ID"],
405
+ client_secret=os.environ["DD_CLIENT_SECRET"],
406
+ a2ui_client=DwsA2UIClient(profile=os.environ["DWS_PROFILE"]),
407
+ ))
408
+
409
+ # 1. 投递 A2UI 卡片 (需要用户的 openDingTalkId 或群 openConversationId)
410
+ card_data = json.loads(Path("example/python/a2ui-card.json").read_text(encoding="utf-8"))
411
+ result = await ch.send_a2ui_card({"open_dingtalk_id": "dt_id..."}, card_data)
412
+
413
+ # 2. 根据服务端 bizId 进行增量更新并置为 FINISH
414
+ update_data = json.loads(Path("example/python/a2ui-update.json").read_text(encoding="utf-8"))
415
+ await ch.update_a2ui_card(result.biz_id, update_data, "FINISH")
416
+ ```
417
+
418
+ ---
419
+
420
+ ## 6. 四语言 API 速查对照
421
+
422
+ | 能力模块 | Python | Node.js | Go | Java |
423
+ |---|---|---|---|---|
424
+ | **创建实例** | `DingTalkChannel(...)` | `new DingTalkChannel(cfg)` | `channel.New(cfg)` | `DingTalkChannel.create(cfg)` |
425
+ | **监听消息** | `@ch.on_message` | `ch.on('message', fn)` | `ch.OnMessage(fn)` | `ch.onMessage(fn)` |
426
+ | **卡片动作** | `@ch.on_card_action` | `ch.on('cardAction', fn)` | `ch.OnCardAction(fn)` | `ch.onCardAction(fn)` |
427
+ | **启动连接** | `await ch.start()` | `await ch.start(signal)` | `ch.Start(ctx)` | `ch.start()` |
428
+ | **优雅关闭** | `ch.close()` | `controller.abort()` | `cancel()` / `bot.Close()` | `ch.close()` |
429
+ | **流式卡片** | `await reply.stream()` | `await reply.stream()` | `reply.Stream(ctx)` | `reply.stream()` |
430
+ | **追加内容** | `await s.append(tok)` | `await s.append(tok)` | `s.Append(tok)` | `s.append(tok)` |
431
+ | **卡片收口** | `await s.finish(md)` | `await s.finish(md)` | `s.Finish(md)` | `s.finish(md)` |
432
+ | **回复文本** | `await reply.text(...)` | `await reply.text(...)` | `reply.Text(ctx, ...)` | `reply.text(...)` |
433
+ | **回复 Markdown**| `await reply.markdown(...)`| `await reply.markdown(...)`| `reply.Markdown(ctx, ...)`| `reply.markdown(...)`|
434
+ | **上传媒体** | `await reply.upload_media(...)`| `await reply.uploadMedia(...)`| `reply.UploadMedia(ctx, ...)`| `reply.uploadMedia(...)`|
435
+ | **主动单聊** | `await ch.send_text(...)`| `await ch.sendText(...)` | `ch.SendText(ctx, ...)`| `ch.sendText(...)` |
436
+ | **主动群聊@**| `await ch.send_markdown(...)`| `await ch.sendMarkdown(...)`| `ch.SendMarkdown(ctx, ...)`| `ch.sendMarkdown(...)`|
437
+ | **A2UI 卡片**| `await ch.send_a2ui_card(...)`| `await ch.sendA2UICard(...)`| `client.SendCard(...)` | `client.sendCard(...)` |
438
+
439
+ ---
440
+
441
+ ## 7. 配套使用示例索引(example/)
442
+
443
+ SDK 仓库在 `example/` 目录下为四门语言提供了完整可运行的代码:
444
+
445
+ ```
446
+ example/
447
+ ├── python/
448
+ │ ├── echo.py # ① 流式打字机卡片极简验证
449
+ │ ├── fullflow.py # ② 全流程:收消息 + 文本回复 + 流式卡片 + 媒体上传 + 主动发消息
450
+ │ ├── livecheck.py # ③ 线上联调逐步检测脚本(PASS/FAIL)
451
+ │ ├── a2ui.py # ④ A2UI 卡片投递与更新示例
452
+ │ ├── a2ui-card.json # A2UI 初始卡片 JSON 结构
453
+ │ └── a2ui-update.json # A2UI 增量更新 JSON 结构
454
+ ├── nodejs/
455
+ │ ├── echo.mjs # ① 流式打字机卡片示例
456
+ │ ├── fullflow.mjs # ② 全流程验证示例
457
+ │ ├── livecheck.mjs # ③ 线上联调测试
458
+ │ ├── a2ui.mjs # ④ A2UI 发送与增量更新
459
+ │ ├── a2ui-card.json
460
+ │ └── a2ui-update.json
461
+ ├── java/
462
+ │ ├── EchoBot.java # ① 流式打字机机器人
463
+ │ ├── FullFlow.java # ② 全流程闭环测试
464
+ │ ├── LiveCheck.java # ③ 联调检测脚本
465
+ │ ├── A2UI.java # ④ A2UI 示例
466
+ │ ├── a2ui-card.json
467
+ │ └── a2ui-update.json
468
+ └── go/
469
+ ├── echo/main.go # ① 流式打字机卡片极简验证
470
+ ├── agent/main.go # ② 接入 DeepSeek/Anthropic 大模型的真实 Agent 示例
471
+ ├── fullflow/main.go # ③ 全流程:被动回复 + 卡片流式 + 媒体上传 + 主动发消息
472
+ ├── livecheck/main.go # ④ 线上联调检测脚本
473
+ ├── a2ui/main.go # ⑤ A2UI 发送与更新示例
474
+ ├── a2ui-card.json
475
+ └── a2ui-update.json
476
+ ```
477
+
478
+ ---
479
+
480
+ ## 8. 真机一键联调验证(LiveCheck)
481
+
482
+ 配置环境变量并在终端运行对应语言的 `livecheck`,给机器人发一句话,控制台会逐步输出 PASS/FAIL 结果,验证全链路联通:
483
+
484
+ ### Go
485
+ ```bash
486
+ DD_CLIENT_ID=ding... DD_CLIENT_SECRET=... go run ./example/go/livecheck
487
+ ```
488
+
489
+ ### Python
490
+ ```bash
491
+ DD_CLIENT_ID=ding... DD_CLIENT_SECRET=... python example/python/livecheck.py
492
+ ```
493
+
494
+ ### Node.js
495
+ ```bash
496
+ DD_CLIENT_ID=ding... DD_CLIENT_SECRET=... node example/nodejs/livecheck.mjs
497
+ ```
498
+
499
+ ### Java
500
+ ```bash
501
+ DD_CLIENT_ID=ding... DD_CLIENT_SECRET=... mvn -q compile exec:java -Dexec.mainClass=com.dingtalk.channel.example.LiveCheck
502
+ ```
503
+
504
+ **验证步骤**:
505
+ 1. `config loaded`:凭据加载
506
+ 2. `stream message received`:长连接建立并成功收取第一条用户消息
507
+ 3. `text reply via sessionWebhook`:验证临时 Token 文本回复链路
508
+ 4. `AI card created & delivered (E1)`:验证卡片创建与投递
509
+ 5. `AI card streaming lifecycle (E2/E3)`:打字机流式输出与终帧 Markdown 定格收口
510
+ 6. (可选通过 `DD_UPLOAD_FILE=/path/to/img.png`)验证 OAPI 媒体上传链路
@@ -0,0 +1,50 @@
1
+ package main
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "fmt"
7
+ "log"
8
+ "os"
9
+
10
+ channel "github.com/DingTalk-Real-AI/dingtalk-channel-sdk-go"
11
+ )
12
+
13
+ // 使用 DWS 的指定 Profile 发送示例卡片并完成原卡片,不需要机器人凭据。
14
+ func main() {
15
+ profile, recipient := os.Getenv("DWS_PROFILE"), os.Getenv("DWS_OPEN_DINGTALK_ID")
16
+ if profile == "" || recipient == "" {
17
+ log.Fatal("请设置 DWS_PROFILE 和 DWS_OPEN_DINGTALK_ID")
18
+ }
19
+ executable := os.Getenv("DWS_BIN")
20
+ if executable == "" {
21
+ executable = "dws"
22
+ }
23
+ client, err := channel.NewDWSA2UIClient(channel.DWSA2UIConfig{Command: []string{executable}, Profile: profile})
24
+ if err != nil {
25
+ log.Fatal(err)
26
+ }
27
+ read := func(name string) []json.RawMessage {
28
+ raw, err := os.ReadFile(name)
29
+ if err != nil {
30
+ log.Fatal(err)
31
+ }
32
+ var messages []json.RawMessage
33
+ if err := json.Unmarshal(raw, &messages); err != nil {
34
+ log.Fatal(err)
35
+ }
36
+ return messages
37
+ }
38
+ ctx := context.Background()
39
+ result, err := client.SendCard(ctx, channel.A2UITarget{OpenDingTalkID: recipient}, read("example/a2ui-card.json"))
40
+ if err != nil {
41
+ log.Fatal(err)
42
+ }
43
+ if result.BizID == "" {
44
+ log.Fatal(result.UpdateWarning)
45
+ }
46
+ if _, err := client.UpdateCard(ctx, result.BizID, read("example/a2ui-update.json"), "FINISH"); err != nil {
47
+ log.Fatal(err)
48
+ }
49
+ fmt.Printf("示例卡片已完成,bizId=%s\n", result.BizID)
50
+ }
@@ -0,0 +1,41 @@
1
+ [
2
+ {
3
+ "version": "v1.0",
4
+ "createSurface": {
5
+ "surfaceId": "channel-sdk-example",
6
+ "catalogId": "https://dingtalk.com/card/a2ui/catalogs/public/catalog.json"
7
+ }
8
+ },
9
+ {
10
+ "version": "v1.0",
11
+ "updateDataModel": {
12
+ "surfaceId": "channel-sdk-example",
13
+ "path": "/",
14
+ "value": {
15
+ "text": "这是 Channel SDK 的 A2UI 示例。"
16
+ }
17
+ }
18
+ },
19
+ {
20
+ "version": "v1.0",
21
+ "updateComponents": {
22
+ "surfaceId": "channel-sdk-example",
23
+ "components": [
24
+ {
25
+ "id": "root",
26
+ "component": "Column",
27
+ "children": [
28
+ "body"
29
+ ]
30
+ },
31
+ {
32
+ "id": "body",
33
+ "component": "Markdown",
34
+ "content": {
35
+ "path": "/text"
36
+ }
37
+ }
38
+ ]
39
+ }
40
+ }
41
+ ]
@@ -0,0 +1,10 @@
1
+ [
2
+ {
3
+ "version": "v1.0",
4
+ "updateDataModel": {
5
+ "surfaceId": "channel-sdk-example",
6
+ "path": "/text",
7
+ "value": "示例已完成;这条消息更新原卡片的数据。"
8
+ }
9
+ }
10
+ ]