@zhin.js/adapter-dingtalk 5.0.5 → 6.0.1

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,43 @@
1
1
  # @zhin.js/adapter-dingtalk
2
2
 
3
+ ## 6.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [c8f4d45]
8
+ - @zhin.js/plugin-runtime@1.1.2
9
+ - @zhin.js/host-http@1.0.5
10
+ - @zhin.js/adapter@1.1.4
11
+ - @zhin.js/agent@1.1.1
12
+ - @zhin.js/command@1.0.6
13
+ - @zhin.js/core@1.5.1
14
+ - zhin.js@6.0.1
15
+
16
+ ## 6.0.0
17
+
18
+ ### Patch Changes
19
+
20
+ - 4fbff5d: feat!: 多模态双向 Segment 一贯制(BREAKING,无兼容层)
21
+
22
+ 全框架唯一媒体表达统一为 canonical `Segment` + `MediaRef{kind: url|path|base64|file, value, mime_type?, file_name?, size?}`,新增 audio/video/file 段类型;所有第二形状(legacy `data.url/file/base64` 字段、`mediaRefFromLegacyData`/`mediaRefToLegacyFields` 桥、双写)全部删除。
23
+
24
+ - **core**:`SendContent` 一等支持 `Segment[]`;endpoint 出站载荷只含 canonical 段;`resolveOutboundMediaPolicy` 改为纯声明驱动(adapter definition `segments.outboundMedia`),内置策略表删除,未声明回退 `url-or-text`;`ImageContent` 旧桥删除。
25
+ - **ai**:新增 `MediaContentBlock`/`MediaBlockRef`(Segment 同构)与 `UserMessage.media`(当前 turn 媒体,**不持久化**——存储层自动剥离);`createUserMessage(text, media?)` 签名变更(`ImageContent` 删除);provider 边界序列化器 `filterMediaBlocksForProvider` + 能力表(缺省 image-only,不支持类型降级占位文本);ai-sdk 桥媒体块 → SDK image/file parts。
26
+ - **agent**:入站 turn 注入(`turn/inbound-media.ts`)——commMessage 媒体段 → 当前 turn `UserMessage.media`;图片 path 物化、音频默认 STT(`@zhin.js/speech` 可选,失败降级占位)、视频/文件占位;`publishOutboundElements` 产出 canonical Segment;`transcribeAudioPayload` 导出。
27
+ - **cli**:`bridgeRuntimeMessage` 回复链路媒体段透传,不再压平为文本(`$reply` 直达 normalize → adapter)。
28
+ - **全部 20 个平台适配器**:出站媒体只消费 `data.media`(url 直发 / base64 直发 / 平台上传 / 读盘),入站媒体产出 canonical `data.media`;`segments.outboundMedia` 声明与实际消费逐一核对修正;QQ 入站新增 canonical segments(image/audio/video/file/mention/face/reply),图片/语音/视频不再丢失。
29
+
30
+ 迁移:适配器/插件产媒体一律用 `{ type, data: { media: MediaRef } }`;发送 legacy `data.url/file/base64` 形状的段会被 warn 丢弃。
31
+
32
+ - Updated dependencies [7c1e63a]
33
+ - Updated dependencies [4fbff5d]
34
+ - Updated dependencies [5b94d9c]
35
+ - @zhin.js/command@1.0.5
36
+ - @zhin.js/adapter@1.1.3
37
+ - @zhin.js/core@1.5.0
38
+ - @zhin.js/agent@1.1.0
39
+ - zhin.js@6.0.0
40
+
3
41
  ## 5.0.5
4
42
 
5
43
  ### Patch Changes
@@ -18,7 +18,7 @@ export default defineAdapter({
18
18
  },
19
19
  create(context) {
20
20
  const config = resolveDingTalkConfig(context.config);
21
- // 注册到插件运行时状态(dingtalk endpoint list 的"运行中"数据源)
21
+ // 注册到插件运行时状态(dingtalk.endpoint list 的"运行中"数据源)
22
22
  context.use(dingtalkRuntimeStateToken).endpoints.set(config.name, {
23
23
  name: config.name,
24
24
  mode: 'webhook',
@@ -23,7 +23,7 @@ export default defineAdapter<DingTalkAdapterConfig>({
23
23
  },
24
24
  create(context) {
25
25
  const config = resolveDingTalkConfig(context.config);
26
- // 注册到插件运行时状态(dingtalk endpoint list 的"运行中"数据源)
26
+ // 注册到插件运行时状态(dingtalk.endpoint list 的"运行中"数据源)
27
27
  context.use(dingtalkRuntimeStateToken).endpoints.set(config.name, {
28
28
  name: config.name,
29
29
  mode: 'webhook',
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `dingtalk endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
2
+ * `dingtalk.endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
3
3
  * commands/endpoint/ 下的 list / add / remove 直接默认导出这三项。
4
4
  */
5
5
  import { createEndpointCommands } from '@zhin.js/adapter';
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * DingTalk 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
3
- * 由 plugin.ts setup() provide,adapter create 与 `dingtalk endpoint` 命令共享(同一 owner generation)。
3
+ * 由 plugin.ts setup() provide,adapter create 与 `dingtalk.endpoint` 命令共享(同一 owner generation)。
4
4
  */
5
5
  import { defineEndpointRuntimeStateToken } from '@zhin.js/adapter';
6
6
  export const dingtalkRuntimeStateToken = defineEndpointRuntimeStateToken('dingtalk');
package/lib/protocol.d.ts CHANGED
@@ -114,7 +114,12 @@ export declare const MAX_TIMESTAMP_DRIFT_MS: number;
114
114
  export declare function verifySignature(appSecret: string, timestamp: string, sign: string): boolean;
115
115
  /**
116
116
  * Wire-encode an already-rendered outbound payload into DingTalk robot body.
117
- * Segment canonicalization is intentionally not done here.
117
+ * Segment canonicalization is intentionally not done here: media segments carry
118
+ * the canonical `data.media` MediaRef and nothing else is consulted.
119
+ *
120
+ * 钉钉机器人媒体消息仅支持远程 URL(picture.picURL):
121
+ * - image 段 kind=url → 直发 picture;
122
+ * - 其余 kind / audio / video / file 段无投递面,warn + 丢弃。
118
123
  */
119
124
  export declare function formatOutboundBody(payload: unknown): DingTalkSendBody;
120
125
  export declare function headerValue(headers: IncomingMessage['headers'], name: string): string;
package/lib/protocol.js CHANGED
@@ -3,6 +3,9 @@
3
3
  * Canonicalization is owned by gateway/core before endpoint.send.
4
4
  */
5
5
  import { createHmac, timingSafeEqual } from 'node:crypto';
6
+ import { isMediaRef } from '@zhin.js/core';
7
+ import { formatCompact, getLogger } from '@zhin.js/logger';
8
+ const logger = getLogger('dingtalk');
6
9
  /**
7
10
  * 钉钉回调消息 @ 机器人判定:机器人被 @ 时回调带 `isInAtList: true`;
8
11
  * 部分回调形态的 `atUserIds` / `atUsers[].dingtalkId` 会包含机器人 robotCode(来自配置)。
@@ -117,7 +120,12 @@ export function verifySignature(appSecret, timestamp, sign) {
117
120
  }
118
121
  /**
119
122
  * Wire-encode an already-rendered outbound payload into DingTalk robot body.
120
- * Segment canonicalization is intentionally not done here.
123
+ * Segment canonicalization is intentionally not done here: media segments carry
124
+ * the canonical `data.media` MediaRef and nothing else is consulted.
125
+ *
126
+ * 钉钉机器人媒体消息仅支持远程 URL(picture.picURL):
127
+ * - image 段 kind=url → 直发 picture;
128
+ * - 其余 kind / audio / video / file 段无投递面,warn + 丢弃。
121
129
  */
122
130
  export function formatOutboundBody(payload) {
123
131
  if (typeof payload === 'string') {
@@ -157,13 +165,33 @@ export function formatOutboundBody(payload) {
157
165
  }
158
166
  break;
159
167
  }
160
- case 'image':
161
- if (!media) {
168
+ case 'image': {
169
+ if (media)
170
+ break;
171
+ const ref = data.media;
172
+ if (isMediaRef(ref) && ref.kind === 'url') {
162
173
  media = {
163
174
  msgtype: 'picture',
164
- picture: { picURL: String(data.url ?? data.file ?? '') },
175
+ picture: { picURL: ref.value },
165
176
  };
177
+ break;
166
178
  }
179
+ logger.warn(formatCompact({
180
+ op: 'dingtalk_outbound_media_dropped',
181
+ type: item.type,
182
+ reason: isMediaRef(ref) ? `unsupported_kind:${ref.kind}` : 'missing_media_ref',
183
+ }));
184
+ break;
185
+ }
186
+ case 'audio':
187
+ case 'video':
188
+ case 'file':
189
+ // 钉钉机器人无音频/视频/文件投递面,warn + 丢弃
190
+ logger.warn(formatCompact({
191
+ op: 'dingtalk_outbound_media_dropped',
192
+ type: item.type,
193
+ reason: 'undeliverable_msgtype',
194
+ }));
167
195
  break;
168
196
  case 'markdown':
169
197
  if (!media) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-dingtalk",
3
- "version": "5.0.5",
3
+ "version": "6.0.1",
4
4
  "description": "Zhin.js DingTalk (钉钉) adapter for Plugin Runtime (HTTP webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -33,21 +33,21 @@
33
33
  "directory": "plugins/adapters/dingtalk"
34
34
  },
35
35
  "dependencies": {
36
- "@zhin.js/adapter": "1.1.2",
37
- "@zhin.js/command": "1.0.4",
38
- "@zhin.js/core": "1.4.3",
39
- "@zhin.js/host-http": "1.0.4",
36
+ "@zhin.js/adapter": "1.1.4",
37
+ "@zhin.js/command": "1.0.6",
38
+ "@zhin.js/core": "1.5.1",
39
+ "@zhin.js/host-http": "1.0.5",
40
40
  "@zhin.js/logger": "1.0.75",
41
- "@zhin.js/plugin-runtime": "1.1.1"
41
+ "@zhin.js/plugin-runtime": "1.1.2"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "zod": "^4.0.0",
45
- "@zhin.js/adapter": "1.1.2",
46
- "@zhin.js/agent": "1.0.10",
47
- "@zhin.js/core": "1.4.3",
48
- "@zhin.js/host-http": "1.0.4",
49
- "@zhin.js/plugin-runtime": "1.1.1",
50
- "zhin.js": "5.0.3"
45
+ "@zhin.js/adapter": "1.1.4",
46
+ "@zhin.js/agent": "1.1.1",
47
+ "@zhin.js/core": "1.5.1",
48
+ "@zhin.js/host-http": "1.0.5",
49
+ "@zhin.js/plugin-runtime": "1.1.2",
50
+ "zhin.js": "6.0.1"
51
51
  },
52
52
  "peerDependenciesMeta": {
53
53
  "zhin.js": {
@@ -65,8 +65,8 @@
65
65
  "typescript": "^6.0.3",
66
66
  "vitest": "^4.1.10",
67
67
  "zod": "^4.4.3",
68
- "@zhin.js/agent": "1.0.10",
69
- "zhin.js": "5.0.3"
68
+ "@zhin.js/agent": "1.1.1",
69
+ "zhin.js": "6.0.1"
70
70
  },
71
71
  "files": [
72
72
  "adapters",
package/plugin.js CHANGED
@@ -9,7 +9,7 @@ export default definePlugin({
9
9
  displayName: 'DingTalk (钉钉) Adapter',
10
10
  },
11
11
  setup(context) {
12
- // 运行中 endpoint 注册表(dingtalk endpoint list 的"运行中"数据源)
12
+ // 运行中 endpoint 注册表(dingtalk.endpoint list 的"运行中"数据源)
13
13
  context.resources.provide(dingtalkRuntimeStateToken, createEndpointRuntimeState());
14
14
  return registerDingtalkPlatformPermitChecker();
15
15
  },
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `dingtalk endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
2
+ * `dingtalk.endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
3
3
  * commands/endpoint/ 下的 list / add / remove 直接默认导出这三项。
4
4
  */
5
5
  import { createEndpointCommands } from '@zhin.js/adapter';
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * DingTalk 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
3
- * 由 plugin.ts setup() provide,adapter create 与 `dingtalk endpoint` 命令共享(同一 owner generation)。
3
+ * 由 plugin.ts setup() provide,adapter create 与 `dingtalk.endpoint` 命令共享(同一 owner generation)。
4
4
  */
5
5
  import { defineEndpointRuntimeStateToken } from '@zhin.js/adapter';
6
6
 
package/src/protocol.ts CHANGED
@@ -6,6 +6,11 @@
6
6
  import { createHmac, timingSafeEqual } from 'node:crypto';
7
7
  import type { IncomingMessage } from 'node:http';
8
8
 
9
+ import { isMediaRef } from '@zhin.js/core';
10
+ import { formatCompact, getLogger } from '@zhin.js/logger';
11
+
12
+ const logger = getLogger('dingtalk');
13
+
9
14
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
10
15
  export interface DingTalkAdapterConfig {
11
16
  readonly name?: string;
@@ -223,7 +228,12 @@ export function verifySignature(
223
228
 
224
229
  /**
225
230
  * Wire-encode an already-rendered outbound payload into DingTalk robot body.
226
- * Segment canonicalization is intentionally not done here.
231
+ * Segment canonicalization is intentionally not done here: media segments carry
232
+ * the canonical `data.media` MediaRef and nothing else is consulted.
233
+ *
234
+ * 钉钉机器人媒体消息仅支持远程 URL(picture.picURL):
235
+ * - image 段 kind=url → 直发 picture;
236
+ * - 其余 kind / audio / video / file 段无投递面,warn + 丢弃。
227
237
  */
228
238
  export function formatOutboundBody(payload: unknown): DingTalkSendBody {
229
239
  if (typeof payload === 'string') {
@@ -267,13 +277,32 @@ export function formatOutboundBody(payload: unknown): DingTalkSendBody {
267
277
  }
268
278
  break;
269
279
  }
270
- case 'image':
271
- if (!media) {
280
+ case 'image': {
281
+ if (media) break;
282
+ const ref = data.media;
283
+ if (isMediaRef(ref) && ref.kind === 'url') {
272
284
  media = {
273
285
  msgtype: 'picture',
274
- picture: { picURL: String(data.url ?? data.file ?? '') },
286
+ picture: { picURL: ref.value },
275
287
  };
288
+ break;
276
289
  }
290
+ logger.warn(formatCompact({
291
+ op: 'dingtalk_outbound_media_dropped',
292
+ type: item.type,
293
+ reason: isMediaRef(ref) ? `unsupported_kind:${ref.kind}` : 'missing_media_ref',
294
+ }));
295
+ break;
296
+ }
297
+ case 'audio':
298
+ case 'video':
299
+ case 'file':
300
+ // 钉钉机器人无音频/视频/文件投递面,warn + 丢弃
301
+ logger.warn(formatCompact({
302
+ op: 'dingtalk_outbound_media_dropped',
303
+ type: item.type,
304
+ reason: 'undeliverable_msgtype',
305
+ }));
277
306
  break;
278
307
  case 'markdown':
279
308
  if (!media) {