@zhin.js/core 1.4.2 → 1.5.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/README.md +3 -3
- package/lib/built/authorization.js +27 -3
- package/lib/built/generic-segment-mapper.d.ts +2 -4
- package/lib/built/generic-segment-mapper.js +64 -106
- package/lib/built/segment-contract/assert.d.ts +4 -1
- package/lib/built/segment-contract/assert.js +15 -6
- package/lib/built/segment-contract/index.d.ts +3 -3
- package/lib/built/segment-contract/index.js +2 -2
- package/lib/built/segment-contract/media.d.ts +1 -7
- package/lib/built/segment-contract/media.js +3 -50
- package/lib/built/segment-contract/preview.js +2 -2
- package/lib/built/segment-contract/types.d.ts +28 -3
- package/lib/built/segment-contract/validate.d.ts +91 -0
- package/lib/built/segment-contract/validate.js +49 -0
- package/lib/plugin-runtime/im/contracts.d.ts +34 -4
- package/lib/plugin-runtime/im/contracts.js +24 -2
- package/lib/plugin-runtime/im/im-runtime.d.ts +28 -9
- package/lib/plugin-runtime/im/im-runtime.js +175 -70
- package/lib/plugin-runtime/im/outbound-renderer.js +4 -1
- package/lib/plugin-runtime/im/outbound-segments.d.ts +13 -14
- package/lib/plugin-runtime/im/outbound-segments.js +37 -65
- package/package.json +8 -7
|
@@ -20,34 +20,33 @@ export interface OutboundSegment {
|
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* 出站媒体能力策略(html→image 渲染产物与 base64/path 媒体段的投递方式):
|
|
23
|
-
* - `base64`:平台接受 base64
|
|
23
|
+
* - `base64`:平台接受 base64 直发;
|
|
24
24
|
* - `url-or-text`:平台仅接受 URL 媒体;本层无上传通道,base64/path → 文本降级;
|
|
25
|
-
* - `passthrough`:adapter
|
|
25
|
+
* - `passthrough`:adapter 自行物化媒体,本层不动。
|
|
26
26
|
*/
|
|
27
27
|
export type OutboundMediaPolicy = 'base64' | 'url-or-text' | 'passthrough';
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
29
|
+
* adapter definition 的 segments policy 声明(必选能力面)。
|
|
30
|
+
* `outboundMedia` 为端点可消费的媒体来源形式数组
|
|
31
|
+
* ('url' | 'path' | 'base64' | 'upload'),映射到投递策略:
|
|
32
|
+
* base64 可直发;无 base64 但端点可自行物化(upload/path)→ passthrough;
|
|
33
|
+
* 仅 url → 非 URL 媒体文本降级。未声明的 adapter 按 ['url'] 兜底。
|
|
34
34
|
*/
|
|
35
35
|
export interface OutboundSegmentsPolicy {
|
|
36
|
-
readonly outboundMedia
|
|
36
|
+
readonly outboundMedia: readonly ('url' | 'path' | 'base64' | 'upload')[];
|
|
37
37
|
readonly interactive?: InteractivePolicy;
|
|
38
38
|
}
|
|
39
39
|
export interface NormalizeOutboundOptions {
|
|
40
|
-
/** 缺省 `
|
|
40
|
+
/** 缺省 `url-or-text`(未声明 adapter 的保守兜底:仅 URL 媒体可达)。 */
|
|
41
41
|
readonly mediaPolicy?: OutboundMediaPolicy;
|
|
42
42
|
}
|
|
43
|
-
export declare function isOutboundSegment(value: unknown): value is OutboundSegment;
|
|
44
43
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* 未知平台回退 `base64`(历史行为)。
|
|
44
|
+
* 解析端点的出站媒体策略:读 adapter definition 声明的 `segments.outboundMedia`
|
|
45
|
+
* (多 endpoint 展开的 `slot~entry` id 回退到 slot id 查声明);
|
|
46
|
+
* 未声明回退 `url-or-text`(仅 URL 媒体可达,其余文本降级)。
|
|
49
47
|
*/
|
|
50
48
|
export declare function resolveOutboundMediaPolicy(adapter: CapabilityId, snapshot: RuntimeSnapshot): OutboundMediaPolicy;
|
|
49
|
+
export declare function isOutboundSegment(value: unknown): value is OutboundSegment;
|
|
51
50
|
/**
|
|
52
51
|
* 解析端点的出站 interactive 策略:优先读 adapter definition 上声明的
|
|
53
52
|
* `segments.interactive`,否则按平台名查内置表,未知平台回退 'text'。
|
|
@@ -5,48 +5,16 @@ import { DEFAULT_INTERACTIVE_POLICY, isKeyboardSegment, } from '../../built/inte
|
|
|
5
5
|
import { isMediaRef } from '../../built/segment-contract/index.js';
|
|
6
6
|
const DEFAULT_CARD_WIDTH = 540;
|
|
7
7
|
const DEFAULT_CARD_FILENAME = 'card.png';
|
|
8
|
-
const DEFAULT_MEDIA_POLICY = '
|
|
9
|
-
/** 平台名 → 出站媒体策略(任务 C 声明式 policy 落地前的内置来源)。 */
|
|
10
|
-
const OUTBOUND_MEDIA_POLICY_BY_ADAPTER = { qq: 'base64',
|
|
11
|
-
icqq: 'base64',
|
|
12
|
-
slack: 'base64',
|
|
13
|
-
'weixin-ilink': 'base64',
|
|
14
|
-
telegram: 'url-or-text',
|
|
15
|
-
line: 'url-or-text',
|
|
16
|
-
lark: 'url-or-text',
|
|
17
|
-
kook: 'url-or-text',
|
|
18
|
-
dingtalk: 'url-or-text',
|
|
19
|
-
'wechat-mp': 'url-or-text',
|
|
20
|
-
wecom: 'url-or-text',
|
|
21
|
-
email: 'url-or-text',
|
|
22
|
-
github: 'url-or-text',
|
|
23
|
-
milky: 'url-or-text',
|
|
24
|
-
napcat: 'passthrough',
|
|
25
|
-
onebot11: 'passthrough',
|
|
26
|
-
onebot12: 'passthrough',
|
|
27
|
-
};
|
|
28
|
-
export function isOutboundSegment(value) {
|
|
29
|
-
return typeof value === 'object'
|
|
30
|
-
&& value !== null
|
|
31
|
-
&& !Array.isArray(value)
|
|
32
|
-
&& typeof value.type === 'string';
|
|
33
|
-
}
|
|
8
|
+
const DEFAULT_MEDIA_POLICY = 'url-or-text';
|
|
34
9
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* 未知平台回退 `base64`(历史行为)。
|
|
10
|
+
* 解析端点的出站媒体策略:读 adapter definition 声明的 `segments.outboundMedia`
|
|
11
|
+
* (多 endpoint 展开的 `slot~entry` id 回退到 slot id 查声明);
|
|
12
|
+
* 未声明回退 `url-or-text`(仅 URL 媒体可达,其余文本降级)。
|
|
39
13
|
*/
|
|
40
14
|
export function resolveOutboundMediaPolicy(adapter, snapshot) {
|
|
41
15
|
const slot = snapshot.capabilities.get(adapter)
|
|
42
16
|
?? snapshot.capabilities.get(baseSlotCapabilityId(adapter));
|
|
43
|
-
|
|
44
|
-
if (declared)
|
|
45
|
-
return declared;
|
|
46
|
-
const packageName = slot ? snapshot.tree.get(slot.owner)?.packageName : undefined;
|
|
47
|
-
const adapterType = adapterTypeName(packageName);
|
|
48
|
-
return (adapterType ? OUTBOUND_MEDIA_POLICY_BY_ADAPTER[adapterType] : undefined)
|
|
49
|
-
?? DEFAULT_MEDIA_POLICY;
|
|
17
|
+
return readDeclaredMediaPolicy(slot?.definition) ?? DEFAULT_MEDIA_POLICY;
|
|
50
18
|
}
|
|
51
19
|
function readDeclaredMediaPolicy(definition) {
|
|
52
20
|
if (!definition || typeof definition !== 'object')
|
|
@@ -55,23 +23,22 @@ function readDeclaredMediaPolicy(definition) {
|
|
|
55
23
|
if (!segments || typeof segments !== 'object')
|
|
56
24
|
return undefined;
|
|
57
25
|
const declared = segments.outboundMedia;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (declared.includes('base64'))
|
|
67
|
-
return 'base64';
|
|
68
|
-
if (declared.includes('upload') || declared.includes('path'))
|
|
69
|
-
return 'passthrough';
|
|
70
|
-
if (declared.includes('url'))
|
|
71
|
-
return 'url-or-text';
|
|
72
|
-
}
|
|
26
|
+
if (!Array.isArray(declared))
|
|
27
|
+
return undefined;
|
|
28
|
+
if (declared.includes('base64'))
|
|
29
|
+
return 'base64';
|
|
30
|
+
if (declared.includes('upload') || declared.includes('path'))
|
|
31
|
+
return 'passthrough';
|
|
32
|
+
if (declared.includes('url'))
|
|
33
|
+
return 'url-or-text';
|
|
73
34
|
return undefined;
|
|
74
35
|
}
|
|
36
|
+
export function isOutboundSegment(value) {
|
|
37
|
+
return typeof value === 'object'
|
|
38
|
+
&& value !== null
|
|
39
|
+
&& !Array.isArray(value)
|
|
40
|
+
&& typeof value.type === 'string';
|
|
41
|
+
}
|
|
75
42
|
/**
|
|
76
43
|
* 平台名 → 出站 interactive 策略(adapter 未声明 `segments.interactive` 时
|
|
77
44
|
* 的内置来源):telegram / discord 端点自行把 keyboard 编码为原生按钮;
|
|
@@ -181,11 +148,12 @@ async function renderHtmlSegment(segment, renderer, mediaPolicy) {
|
|
|
181
148
|
return {
|
|
182
149
|
type: 'image',
|
|
183
150
|
data: {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
151
|
+
media: {
|
|
152
|
+
kind: 'base64',
|
|
153
|
+
value: base64,
|
|
154
|
+
mime_type: 'image/png',
|
|
155
|
+
file_name: typeof data.fileName === 'string' ? data.fileName : DEFAULT_CARD_FILENAME,
|
|
156
|
+
},
|
|
189
157
|
},
|
|
190
158
|
};
|
|
191
159
|
}
|
|
@@ -197,22 +165,26 @@ async function renderHtmlSegment(segment, renderer, mediaPolicy) {
|
|
|
197
165
|
return { type: 'text', data: { text: htmlSegmentFallbackText(data, html) } };
|
|
198
166
|
}
|
|
199
167
|
/**
|
|
200
|
-
* 媒体协商:仅 `url-or-text`
|
|
201
|
-
* (base64 / 本地路径)无法投递,降级为文本(alt 优先)。
|
|
168
|
+
* 媒体协商:仅 `url-or-text` 需要在本层动作——媒体段的非 URL MediaRef
|
|
169
|
+
* (base64 / 本地路径)无法投递,降级为文本(alt / file_name 优先)。
|
|
202
170
|
*/
|
|
171
|
+
const MEDIA_SEGMENT_TYPES = new Set(['image', 'audio', 'video', 'file']);
|
|
203
172
|
function applyOutboundMediaPolicy(segments, mediaPolicy) {
|
|
204
173
|
if (mediaPolicy !== 'url-or-text')
|
|
205
174
|
return segments;
|
|
206
175
|
return segments.map((segment) => {
|
|
207
|
-
if (segment.type
|
|
176
|
+
if (!MEDIA_SEGMENT_TYPES.has(segment.type))
|
|
208
177
|
return segment;
|
|
209
|
-
const
|
|
178
|
+
const data = segment.data;
|
|
179
|
+
const media = data.media;
|
|
210
180
|
if (!isMediaRef(media) || media.kind === 'url')
|
|
211
181
|
return segment;
|
|
212
|
-
const
|
|
213
|
-
?
|
|
214
|
-
: '
|
|
215
|
-
|
|
182
|
+
const label = typeof data.alt === 'string' && data.alt
|
|
183
|
+
? data.alt
|
|
184
|
+
: typeof data.name === 'string' && data.name
|
|
185
|
+
? data.name
|
|
186
|
+
: media.file_name ?? `[${segment.type}]`;
|
|
187
|
+
return { type: 'text', data: { text: label } };
|
|
216
188
|
});
|
|
217
189
|
}
|
|
218
190
|
function htmlSegmentFallbackText(data, html) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Zhin机器人核心框架",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -68,12 +68,13 @@
|
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"qrcode": "^1.5.4",
|
|
70
70
|
"segment-matcher": "^1.0.5",
|
|
71
|
-
"smol-toml": "^1.7.
|
|
71
|
+
"smol-toml": "^1.7.1",
|
|
72
72
|
"yaml": "^2.9.0",
|
|
73
|
-
"@zhin.js/adapter": "1.1.
|
|
74
|
-
"@zhin.js/command": "1.0.
|
|
73
|
+
"@zhin.js/adapter": "1.1.3",
|
|
74
|
+
"@zhin.js/command": "1.0.5",
|
|
75
75
|
"@zhin.js/component": "1.0.4",
|
|
76
76
|
"@zhin.js/database": "1.0.78",
|
|
77
|
+
"@zhin.js/im-contract": "1.0.0",
|
|
77
78
|
"@zhin.js/kernel": "1.0.5",
|
|
78
79
|
"@zhin.js/logger": "1.0.75",
|
|
79
80
|
"@zhin.js/middleware": "1.0.4",
|
|
@@ -89,11 +90,11 @@
|
|
|
89
90
|
}
|
|
90
91
|
},
|
|
91
92
|
"devDependencies": {
|
|
92
|
-
"@types/node": "^26.1.
|
|
93
|
-
"@types/qrcode": "^1.5.
|
|
93
|
+
"@types/node": "^26.1.2",
|
|
94
|
+
"@types/qrcode": "^1.5.6",
|
|
94
95
|
"ajv": "8.18.0",
|
|
95
96
|
"typescript": "^6.0.3",
|
|
96
|
-
"@zhin.js/ai": "1.
|
|
97
|
+
"@zhin.js/ai": "1.5.0"
|
|
97
98
|
},
|
|
98
99
|
"repository": {
|
|
99
100
|
"type": "git",
|