@zhin.js/adapter-github 4.0.4 → 5.0.0

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,40 @@
1
1
  # @zhin.js/adapter-github
2
2
 
3
+ ## 5.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 4fbff5d: feat!: 多模态双向 Segment 一贯制(BREAKING,无兼容层)
8
+
9
+ 全框架唯一媒体表达统一为 canonical `Segment` + `MediaRef{kind: url|path|base64|file, value, mime_type?, file_name?, size?}`,新增 audio/video/file 段类型;所有第二形状(legacy `data.url/file/base64` 字段、`mediaRefFromLegacyData`/`mediaRefToLegacyFields` 桥、双写)全部删除。
10
+
11
+ - **core**:`SendContent` 一等支持 `Segment[]`;endpoint 出站载荷只含 canonical 段;`resolveOutboundMediaPolicy` 改为纯声明驱动(adapter definition `segments.outboundMedia`),内置策略表删除,未声明回退 `url-or-text`;`ImageContent` 旧桥删除。
12
+ - **ai**:新增 `MediaContentBlock`/`MediaBlockRef`(Segment 同构)与 `UserMessage.media`(当前 turn 媒体,**不持久化**——存储层自动剥离);`createUserMessage(text, media?)` 签名变更(`ImageContent` 删除);provider 边界序列化器 `filterMediaBlocksForProvider` + 能力表(缺省 image-only,不支持类型降级占位文本);ai-sdk 桥媒体块 → SDK image/file parts。
13
+ - **agent**:入站 turn 注入(`turn/inbound-media.ts`)——commMessage 媒体段 → 当前 turn `UserMessage.media`;图片 path 物化、音频默认 STT(`@zhin.js/speech` 可选,失败降级占位)、视频/文件占位;`publishOutboundElements` 产出 canonical Segment;`transcribeAudioPayload` 导出。
14
+ - **cli**:`bridgeRuntimeMessage` 回复链路媒体段透传,不再压平为文本(`$reply` 直达 normalize → adapter)。
15
+ - **全部 20 个平台适配器**:出站媒体只消费 `data.media`(url 直发 / base64 直发 / 平台上传 / 读盘),入站媒体产出 canonical `data.media`;`segments.outboundMedia` 声明与实际消费逐一核对修正;QQ 入站新增 canonical segments(image/audio/video/file/mention/face/reply),图片/语音/视频不再丢失。
16
+
17
+ 迁移:适配器/插件产媒体一律用 `{ type, data: { media: MediaRef } }`;发送 legacy `data.url/file/base64` 形状的段会被 warn 丢弃。
18
+
19
+ - Updated dependencies [7c1e63a]
20
+ - Updated dependencies [4fbff5d]
21
+ - Updated dependencies [5b94d9c]
22
+ - @zhin.js/command@1.0.5
23
+ - @zhin.js/adapter@1.1.3
24
+ - @zhin.js/core@1.5.0
25
+ - @zhin.js/agent@1.1.0
26
+ - zhin.js@6.0.0
27
+
28
+ ## 4.0.5
29
+
30
+ ### Patch Changes
31
+
32
+ - Updated dependencies [d8bf702]
33
+ - @zhin.js/host-http@1.0.4
34
+ - @zhin.js/agent@1.0.10
35
+ - @zhin.js/core@1.4.3
36
+ - zhin.js@5.0.3
37
+
3
38
  ## 4.0.4
4
39
 
5
40
  ### Patch Changes
@@ -29,7 +29,7 @@ export default defineAdapter({
29
29
  create(context) {
30
30
  const config = resolveGithubConfig(context.config);
31
31
  const database = optionalDatabase(context);
32
- // 注册到插件运行时状态(github endpoint list 的"运行中"数据源)
32
+ // 注册到插件运行时状态(github.endpoint list 的"运行中"数据源)
33
33
  context.use(githubRuntimeStateToken).endpoints.set(config.name, {
34
34
  name: config.name,
35
35
  mode: config.webhookSecret ? 'webhook' : 'api',
@@ -37,7 +37,7 @@ export default defineAdapter<GithubAdapterConfig>({
37
37
  create(context) {
38
38
  const config = resolveGithubConfig(context.config);
39
39
  const database = optionalDatabase(context);
40
- // 注册到插件运行时状态(github endpoint list 的"运行中"数据源)
40
+ // 注册到插件运行时状态(github.endpoint list 的"运行中"数据源)
41
41
  context.use(githubRuntimeStateToken).endpoints.set(config.name, {
42
42
  name: config.name,
43
43
  mode: config.webhookSecret ? 'webhook' : 'api',
@@ -1,10 +1,10 @@
1
1
  /**
2
- * `github endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
2
+ * `github.endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
3
3
  * commands/endpoint/ 下的 list / add / remove 直接默认导出这三项。
4
4
  *
5
5
  * 字段说明:private_key 内容长且含换行,不适合 kv 直传——运行时(gh-client
6
6
  * resolvePrivateKey)本就支持 PEM 内容或文件路径,故 add 采用**内联路径**形式:
7
- * `github endpoint add mybot app_id=123456 private_key=./data/mybot.pem`。
7
+ * `github.endpoint add mybot app_id=123456 private_key=./data/mybot.pem`。
8
8
  */
9
9
  import { createEndpointCommands } from '@zhin.js/adapter';
10
10
  import { defineCommand } from '@zhin.js/command';
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * GitHub 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
3
- * 由 plugin.ts setup() provide,adapter create 与 `github endpoint` 命令共享(同一 owner generation)。
3
+ * 由 plugin.ts setup() provide,adapter create 与 `github.endpoint` 命令共享(同一 owner generation)。
4
4
  */
5
5
  import { defineEndpointRuntimeStateToken } from '@zhin.js/adapter';
6
6
  export const githubRuntimeStateToken = defineEndpointRuntimeStateToken('github');
package/lib/protocol.js CHANGED
@@ -4,6 +4,9 @@
4
4
  */
5
5
  import { createHmac, timingSafeEqual } from 'node:crypto';
6
6
  import { pickCredential } from '@zhin.js/adapter';
7
+ import { isMediaRef } from '@zhin.js/core';
8
+ import { formatCompact, getLogger } from '@zhin.js/logger';
9
+ const logger = getLogger('github');
7
10
  import { buildChannelId, } from './types.js';
8
11
  export { buildChannelId, parseChannelId } from './types.js';
9
12
  export function resolveGithubConfig(config = {}) {
@@ -94,7 +97,10 @@ export function formatOutboundBody(payload) {
94
97
  case 'at':
95
98
  return `@${item.data?.name || item.data?.id || item.data?.target || ''}`;
96
99
  case 'image':
97
- return item.data?.url ? `![image](${item.data.url})` : '[image]';
100
+ case 'audio':
101
+ case 'video':
102
+ case 'file':
103
+ return formatMediaSegment(item);
98
104
  case 'link':
99
105
  return `[${item.data?.text || item.data?.url || ''}](${item.data?.url || ''})`;
100
106
  default:
@@ -102,6 +108,38 @@ export function formatOutboundBody(payload) {
102
108
  }
103
109
  }).join('');
104
110
  }
111
+ /**
112
+ * 媒体段 → GitHub markdown(canonical MediaRef 唯一来源)。
113
+ * GitHub 评论只能消费远程 URL(无上传面),kind=url 直发;
114
+ * base64 / path / file 不可投递,warn + 丢弃。
115
+ */
116
+ function formatMediaSegment(seg) {
117
+ const media = seg.data?.media;
118
+ if (!isMediaRef(media)) {
119
+ logger.warn(formatCompact({
120
+ op: 'github_outbound_media_dropped',
121
+ type: seg.type,
122
+ reason: 'missing_media_ref',
123
+ }));
124
+ return '';
125
+ }
126
+ if (media.kind !== 'url') {
127
+ logger.warn(formatCompact({
128
+ op: 'github_outbound_media_dropped',
129
+ type: seg.type,
130
+ reason: `unsupported_kind:${media.kind}`,
131
+ }));
132
+ return '';
133
+ }
134
+ if (seg.type === 'image') {
135
+ const alt = typeof seg.data?.alt === 'string' && seg.data.alt
136
+ ? seg.data.alt
137
+ : media.file_name || 'image';
138
+ return `![${alt}](${media.value})`;
139
+ }
140
+ const label = media.file_name || seg.type;
141
+ return `[${label}](${media.value})`;
142
+ }
105
143
  export function verifyWebhookSignature(secret, rawBody, signatureHeader) {
106
144
  if (!signatureHeader?.startsWith('sha256='))
107
145
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-github",
3
- "version": "4.0.4",
3
+ "version": "5.0.0",
4
4
  "description": "Zhin.js GitHub adapter for Plugin Runtime (App auth + webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -41,21 +41,21 @@
41
41
  "directory": "plugins/adapters/github"
42
42
  },
43
43
  "dependencies": {
44
- "@zhin.js/adapter": "1.1.2",
45
- "@zhin.js/command": "1.0.4",
46
- "@zhin.js/core": "1.4.3",
47
- "@zhin.js/host-http": "1.0.3",
44
+ "@zhin.js/adapter": "1.1.3",
45
+ "@zhin.js/command": "1.0.5",
46
+ "@zhin.js/core": "1.5.0",
47
+ "@zhin.js/host-http": "1.0.4",
48
48
  "@zhin.js/logger": "1.0.75",
49
49
  "@zhin.js/plugin-runtime": "1.1.1"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "zod": "^4.0.0",
53
- "@zhin.js/adapter": "1.1.2",
54
- "@zhin.js/agent": "1.0.9",
55
- "@zhin.js/core": "1.4.3",
56
- "@zhin.js/host-http": "1.0.3",
53
+ "@zhin.js/adapter": "1.1.3",
54
+ "@zhin.js/agent": "1.1.0",
55
+ "@zhin.js/core": "1.5.0",
56
+ "@zhin.js/host-http": "1.0.4",
57
57
  "@zhin.js/plugin-runtime": "1.1.1",
58
- "zhin.js": "5.0.3"
58
+ "zhin.js": "6.0.0"
59
59
  },
60
60
  "peerDependenciesMeta": {
61
61
  "zhin.js": {
@@ -69,12 +69,12 @@
69
69
  }
70
70
  },
71
71
  "devDependencies": {
72
- "@types/node": "^26.1.0",
72
+ "@types/node": "^26.1.2",
73
73
  "typescript": "^6.0.3",
74
74
  "vitest": "^4.1.10",
75
75
  "zod": "^4.4.3",
76
- "@zhin.js/agent": "1.0.9",
77
- "zhin.js": "5.0.3"
76
+ "@zhin.js/agent": "1.1.0",
77
+ "zhin.js": "6.0.0"
78
78
  },
79
79
  "files": [
80
80
  "adapters",
package/plugin.js CHANGED
@@ -27,7 +27,7 @@ export default definePlugin({
27
27
  displayName: 'GitHub Adapter',
28
28
  },
29
29
  setup(context) {
30
- // 运行中 endpoint 注册表(github endpoint list 的"运行中"数据源)
30
+ // 运行中 endpoint 注册表(github.endpoint list 的"运行中"数据源)
31
31
  context.resources.provide(githubRuntimeStateToken, createEndpointRuntimeState());
32
32
  if (context.resources.has(databaseHostToken)) {
33
33
  const host = context.resources.use(databaseHostToken);
@@ -1,10 +1,10 @@
1
1
  /**
2
- * `github endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
2
+ * `github.endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
3
3
  * commands/endpoint/ 下的 list / add / remove 直接默认导出这三项。
4
4
  *
5
5
  * 字段说明:private_key 内容长且含换行,不适合 kv 直传——运行时(gh-client
6
6
  * resolvePrivateKey)本就支持 PEM 内容或文件路径,故 add 采用**内联路径**形式:
7
- * `github endpoint add mybot app_id=123456 private_key=./data/mybot.pem`。
7
+ * `github.endpoint add mybot app_id=123456 private_key=./data/mybot.pem`。
8
8
  */
9
9
  import { createEndpointCommands } from '@zhin.js/adapter';
10
10
  import { defineCommand } from '@zhin.js/command';
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * GitHub 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
3
- * 由 plugin.ts setup() provide,adapter create 与 `github endpoint` 命令共享(同一 owner generation)。
3
+ * 由 plugin.ts setup() provide,adapter create 与 `github.endpoint` 命令共享(同一 owner generation)。
4
4
  */
5
5
  import { defineEndpointRuntimeStateToken } from '@zhin.js/adapter';
6
6
 
package/src/protocol.ts CHANGED
@@ -6,6 +6,10 @@
6
6
  import { createHmac, timingSafeEqual } from 'node:crypto';
7
7
  import type { IncomingMessage } from 'node:http';
8
8
  import { pickCredential } from '@zhin.js/adapter';
9
+ import { isMediaRef } from '@zhin.js/core';
10
+ import { formatCompact, getLogger } from '@zhin.js/logger';
11
+
12
+ const logger = getLogger('github');
9
13
  import {
10
14
  buildChannelId,
11
15
  type GenericWebhookPayload,
@@ -209,7 +213,10 @@ export function formatOutboundBody(payload: unknown): string {
209
213
  case 'at':
210
214
  return `@${item.data?.name || item.data?.id || item.data?.target || ''}`;
211
215
  case 'image':
212
- return item.data?.url ? `![image](${item.data.url})` : '[image]';
216
+ case 'audio':
217
+ case 'video':
218
+ case 'file':
219
+ return formatMediaSegment(item);
213
220
  case 'link':
214
221
  return `[${item.data?.text || item.data?.url || ''}](${item.data?.url || ''})`;
215
222
  default:
@@ -218,6 +225,39 @@ export function formatOutboundBody(payload: unknown): string {
218
225
  }).join('');
219
226
  }
220
227
 
228
+ /**
229
+ * 媒体段 → GitHub markdown(canonical MediaRef 唯一来源)。
230
+ * GitHub 评论只能消费远程 URL(无上传面),kind=url 直发;
231
+ * base64 / path / file 不可投递,warn + 丢弃。
232
+ */
233
+ function formatMediaSegment(seg: GithubWireSegment): string {
234
+ const media = seg.data?.media;
235
+ if (!isMediaRef(media)) {
236
+ logger.warn(formatCompact({
237
+ op: 'github_outbound_media_dropped',
238
+ type: seg.type,
239
+ reason: 'missing_media_ref',
240
+ }));
241
+ return '';
242
+ }
243
+ if (media.kind !== 'url') {
244
+ logger.warn(formatCompact({
245
+ op: 'github_outbound_media_dropped',
246
+ type: seg.type,
247
+ reason: `unsupported_kind:${media.kind}`,
248
+ }));
249
+ return '';
250
+ }
251
+ if (seg.type === 'image') {
252
+ const alt = typeof seg.data?.alt === 'string' && seg.data.alt
253
+ ? seg.data.alt
254
+ : media.file_name || 'image';
255
+ return `![${alt}](${media.value})`;
256
+ }
257
+ const label = media.file_name || seg.type;
258
+ return `[${label}](${media.value})`;
259
+ }
260
+
221
261
  export function verifyWebhookSignature(
222
262
  secret: string,
223
263
  rawBody: string,