@zhin.js/adapter-milky 5.0.5 → 6.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 +25 -0
- package/adapters/milky.js +1 -1
- package/adapters/milky.ts +1 -1
- package/lib/milky-endpoint-commands.js +1 -1
- package/lib/milky-runtime-state.js +1 -1
- package/lib/protocol.d.ts +2 -0
- package/lib/protocol.js +59 -17
- package/package.json +9 -9
- package/plugin.js +1 -1
- package/src/milky-endpoint-commands.ts +1 -1
- package/src/milky-runtime-state.ts +1 -1
- package/src/protocol.ts +59 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @zhin.js/adapter-milky
|
|
2
2
|
|
|
3
|
+
## 6.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
|
+
|
|
3
28
|
## 5.0.5
|
|
4
29
|
|
|
5
30
|
### Patch Changes
|
package/adapters/milky.js
CHANGED
|
@@ -19,7 +19,7 @@ export default defineAdapter({
|
|
|
19
19
|
create(context) {
|
|
20
20
|
const config = resolveMilkyConfig(context.config);
|
|
21
21
|
const gateway = context.use(messageGatewayToken);
|
|
22
|
-
// 注册到插件运行时状态(milky
|
|
22
|
+
// 注册到插件运行时状态(milky.endpoint list 的"运行中"数据源)
|
|
23
23
|
context.use(milkyRuntimeStateToken).endpoints.set(config.name, {
|
|
24
24
|
name: config.name,
|
|
25
25
|
mode: config.connection,
|
package/adapters/milky.ts
CHANGED
|
@@ -42,7 +42,7 @@ export default defineAdapter<MilkyAdapterConfig>({
|
|
|
42
42
|
create(context) {
|
|
43
43
|
const config = resolveMilkyConfig(context.config);
|
|
44
44
|
const gateway = context.use(messageGatewayToken);
|
|
45
|
-
// 注册到插件运行时状态(milky
|
|
45
|
+
// 注册到插件运行时状态(milky.endpoint list 的"运行中"数据源)
|
|
46
46
|
context.use(milkyRuntimeStateToken).endpoints.set(config.name, {
|
|
47
47
|
name: config.name,
|
|
48
48
|
mode: config.connection,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `milky
|
|
2
|
+
* `milky.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
|
* Milky 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
|
|
3
|
-
* 由 plugin.ts setup() provide,adapter create 与 `milky
|
|
3
|
+
* 由 plugin.ts setup() provide,adapter create 与 `milky.endpoint` 命令共享(同一 owner generation)。
|
|
4
4
|
*/
|
|
5
5
|
import { defineEndpointRuntimeStateToken } from '@zhin.js/adapter';
|
|
6
6
|
export const milkyRuntimeStateToken = defineEndpointRuntimeStateToken('milky');
|
package/lib/protocol.d.ts
CHANGED
|
@@ -143,6 +143,8 @@ export declare function parseSendTarget(target: string): ParsedSendTarget;
|
|
|
143
143
|
/**
|
|
144
144
|
* Wire-encode an already-rendered outbound payload into Milky outgoing segments.
|
|
145
145
|
* Segment canonicalization is intentionally not done here.
|
|
146
|
+
* 媒体段(image/audio/record/video)只消费 canonical `data.media` MediaRef;
|
|
147
|
+
* file 段 Milky 消息 API 无对应 wire 段(文件走 upload_* API),warn + 丢弃。
|
|
146
148
|
*/
|
|
147
149
|
export declare function formatOutboundSegments(payload: unknown): MilkyOutgoingSegment[];
|
|
148
150
|
export declare function buildSendAction(target: string, message: MilkyOutgoingSegment[]): {
|
package/lib/protocol.js
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
4
|
* Spec: https://milky.ntqqrev.org/
|
|
5
5
|
*/
|
|
6
|
+
import { isMediaRef } from '@zhin.js/core';
|
|
7
|
+
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
8
|
+
const logger = getLogger('milky');
|
|
6
9
|
function normalizeConnection(connection) {
|
|
7
10
|
if (connection === 'sse' || connection === 'webhook' || connection === 'wss')
|
|
8
11
|
return connection;
|
|
@@ -300,9 +303,52 @@ export function parseSendTarget(target) {
|
|
|
300
303
|
return { message_type: 'private', id: rest };
|
|
301
304
|
return { message_type: 'private', id: target };
|
|
302
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* 解析 image/audio/video 段的投递 uri(canonical MediaRef 唯一来源):
|
|
308
|
+
* - kind=url → http(s):// 直发;
|
|
309
|
+
* - kind=base64 → base64:// 直发;
|
|
310
|
+
* - kind=path / file → Milky 资源 uri 仅消费 http(s):// 与 base64://,不支持,丢弃。
|
|
311
|
+
*/
|
|
312
|
+
function resolveMediaUri(data) {
|
|
313
|
+
const media = data.media;
|
|
314
|
+
if (!isMediaRef(media))
|
|
315
|
+
return undefined;
|
|
316
|
+
if (media.kind === 'url')
|
|
317
|
+
return media.value;
|
|
318
|
+
if (media.kind === 'base64') {
|
|
319
|
+
return media.value.startsWith('base64://') ? media.value : `base64://${media.value}`;
|
|
320
|
+
}
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
/** 媒体段 → Milky 资源段(image/record/video);不可投递时 warn + 丢弃。 */
|
|
324
|
+
function normalizeMediaSegment(seg) {
|
|
325
|
+
const data = seg.data ?? {};
|
|
326
|
+
const wireType = seg.type === 'image'
|
|
327
|
+
? 'image'
|
|
328
|
+
: seg.type === 'video'
|
|
329
|
+
? 'video'
|
|
330
|
+
: 'record';
|
|
331
|
+
const uri = resolveMediaUri(data);
|
|
332
|
+
if (!uri) {
|
|
333
|
+
const media = data.media;
|
|
334
|
+
logger.warn(formatCompact({
|
|
335
|
+
op: 'milky_outbound_media_dropped',
|
|
336
|
+
type: seg.type,
|
|
337
|
+
reason: isMediaRef(media) ? 'unsupported_media_kind' : 'missing_media_ref',
|
|
338
|
+
...(isMediaRef(media) ? { kind: media.kind } : {}),
|
|
339
|
+
}));
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
type: wireType,
|
|
344
|
+
data: wireType === 'video' ? { uri, thumb_uri: data.thumb_uri } : { uri },
|
|
345
|
+
};
|
|
346
|
+
}
|
|
303
347
|
/**
|
|
304
348
|
* Wire-encode an already-rendered outbound payload into Milky outgoing segments.
|
|
305
349
|
* Segment canonicalization is intentionally not done here.
|
|
350
|
+
* 媒体段(image/audio/record/video)只消费 canonical `data.media` MediaRef;
|
|
351
|
+
* file 段 Milky 消息 API 无对应 wire 段(文件走 upload_* API),warn + 丢弃。
|
|
306
352
|
*/
|
|
307
353
|
export function formatOutboundSegments(payload) {
|
|
308
354
|
if (typeof payload === 'string') {
|
|
@@ -364,25 +410,21 @@ export function formatOutboundSegments(payload) {
|
|
|
364
410
|
break;
|
|
365
411
|
}
|
|
366
412
|
case 'image':
|
|
367
|
-
out.push({
|
|
368
|
-
type: 'image',
|
|
369
|
-
data: { uri: String(data.url ?? data.uri ?? '') },
|
|
370
|
-
});
|
|
371
|
-
break;
|
|
372
413
|
case 'record':
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
414
|
+
case 'audio':
|
|
415
|
+
case 'video': {
|
|
416
|
+
const media = normalizeMediaSegment(item);
|
|
417
|
+
if (media)
|
|
418
|
+
out.push(media);
|
|
377
419
|
break;
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
});
|
|
420
|
+
}
|
|
421
|
+
case 'file':
|
|
422
|
+
// Milky 消息 API 无 file 段(文件走 upload_private/group_file),无法投递
|
|
423
|
+
logger.warn(formatCompact({
|
|
424
|
+
op: 'milky_outbound_media_dropped',
|
|
425
|
+
type,
|
|
426
|
+
reason: 'unsupported_segment',
|
|
427
|
+
}));
|
|
386
428
|
break;
|
|
387
429
|
default:
|
|
388
430
|
out.push({ type, data: data });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-milky",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Zhin.js Milky adapter for Plugin Runtime (WebSocket client)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"ws": "^8.21.1",
|
|
43
|
-
"@zhin.js/adapter": "1.1.
|
|
44
|
-
"@zhin.js/command": "1.0.
|
|
45
|
-
"@zhin.js/core": "1.
|
|
43
|
+
"@zhin.js/adapter": "1.1.3",
|
|
44
|
+
"@zhin.js/command": "1.0.5",
|
|
45
|
+
"@zhin.js/core": "1.5.0",
|
|
46
46
|
"@zhin.js/host-http": "1.0.4",
|
|
47
47
|
"@zhin.js/logger": "1.0.75",
|
|
48
48
|
"@zhin.js/plugin-runtime": "1.1.1"
|
|
@@ -53,16 +53,16 @@
|
|
|
53
53
|
"typescript": "^6.0.3",
|
|
54
54
|
"vitest": "^4.1.10",
|
|
55
55
|
"zod": "^4.4.3",
|
|
56
|
-
"@zhin.js/agent": "1.0
|
|
56
|
+
"@zhin.js/agent": "1.1.0",
|
|
57
57
|
"@zhin.js/host-http": "1.0.4"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"zod": "^4.0.0",
|
|
61
|
-
"@zhin.js/adapter": "1.1.
|
|
62
|
-
"@zhin.js/agent": "1.0
|
|
63
|
-
"@zhin.js/core": "1.
|
|
61
|
+
"@zhin.js/adapter": "1.1.3",
|
|
62
|
+
"@zhin.js/agent": "1.1.0",
|
|
63
|
+
"@zhin.js/core": "1.5.0",
|
|
64
64
|
"@zhin.js/plugin-runtime": "1.1.1",
|
|
65
|
-
"zhin.js": "
|
|
65
|
+
"zhin.js": "6.0.0"
|
|
66
66
|
},
|
|
67
67
|
"peerDependenciesMeta": {
|
|
68
68
|
"zhin.js": {
|
package/plugin.js
CHANGED
|
@@ -8,7 +8,7 @@ export default definePlugin({
|
|
|
8
8
|
displayName: 'Milky Adapter',
|
|
9
9
|
},
|
|
10
10
|
setup(context) {
|
|
11
|
-
// 运行中 endpoint 注册表(milky
|
|
11
|
+
// 运行中 endpoint 注册表(milky.endpoint list 的"运行中"数据源)
|
|
12
12
|
context.resources.provide(milkyRuntimeStateToken, createEndpointRuntimeState());
|
|
13
13
|
},
|
|
14
14
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `milky
|
|
2
|
+
* `milky.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
|
* Milky 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
|
|
3
|
-
* 由 plugin.ts setup() provide,adapter create 与 `milky
|
|
3
|
+
* 由 plugin.ts setup() provide,adapter create 与 `milky.endpoint` 命令共享(同一 owner generation)。
|
|
4
4
|
*/
|
|
5
5
|
import { defineEndpointRuntimeStateToken } from '@zhin.js/adapter';
|
|
6
6
|
|
package/src/protocol.ts
CHANGED
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
* Spec: https://milky.ntqqrev.org/
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { isMediaRef } from '@zhin.js/core';
|
|
7
8
|
import type { Segment } from '@zhin.js/core/runtime';
|
|
9
|
+
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
10
|
+
|
|
11
|
+
const logger = getLogger('milky');
|
|
8
12
|
|
|
9
13
|
/** Transitional legacy endpoint row (`endpoints[]` with `context: milky`). */
|
|
10
14
|
export interface MilkyLegacyEndpointRow {
|
|
@@ -435,9 +439,52 @@ export function parseSendTarget(target: string): ParsedSendTarget {
|
|
|
435
439
|
return { message_type: 'private', id: target };
|
|
436
440
|
}
|
|
437
441
|
|
|
442
|
+
/**
|
|
443
|
+
* 解析 image/audio/video 段的投递 uri(canonical MediaRef 唯一来源):
|
|
444
|
+
* - kind=url → http(s):// 直发;
|
|
445
|
+
* - kind=base64 → base64:// 直发;
|
|
446
|
+
* - kind=path / file → Milky 资源 uri 仅消费 http(s):// 与 base64://,不支持,丢弃。
|
|
447
|
+
*/
|
|
448
|
+
function resolveMediaUri(data: Record<string, unknown>): string | undefined {
|
|
449
|
+
const media = data.media;
|
|
450
|
+
if (!isMediaRef(media)) return undefined;
|
|
451
|
+
if (media.kind === 'url') return media.value;
|
|
452
|
+
if (media.kind === 'base64') {
|
|
453
|
+
return media.value.startsWith('base64://') ? media.value : `base64://${media.value}`;
|
|
454
|
+
}
|
|
455
|
+
return undefined;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** 媒体段 → Milky 资源段(image/record/video);不可投递时 warn + 丢弃。 */
|
|
459
|
+
function normalizeMediaSegment(seg: MilkyWireSegment): MilkyOutgoingSegment | null {
|
|
460
|
+
const data = seg.data ?? {};
|
|
461
|
+
const wireType = seg.type === 'image'
|
|
462
|
+
? 'image'
|
|
463
|
+
: seg.type === 'video'
|
|
464
|
+
? 'video'
|
|
465
|
+
: 'record';
|
|
466
|
+
const uri = resolveMediaUri(data);
|
|
467
|
+
if (!uri) {
|
|
468
|
+
const media = data.media;
|
|
469
|
+
logger.warn(formatCompact({
|
|
470
|
+
op: 'milky_outbound_media_dropped',
|
|
471
|
+
type: seg.type,
|
|
472
|
+
reason: isMediaRef(media) ? 'unsupported_media_kind' : 'missing_media_ref',
|
|
473
|
+
...(isMediaRef(media) ? { kind: media.kind } : {}),
|
|
474
|
+
}));
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
return {
|
|
478
|
+
type: wireType,
|
|
479
|
+
data: wireType === 'video' ? { uri, thumb_uri: data.thumb_uri } : { uri },
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
438
483
|
/**
|
|
439
484
|
* Wire-encode an already-rendered outbound payload into Milky outgoing segments.
|
|
440
485
|
* Segment canonicalization is intentionally not done here.
|
|
486
|
+
* 媒体段(image/audio/record/video)只消费 canonical `data.media` MediaRef;
|
|
487
|
+
* file 段 Milky 消息 API 无对应 wire 段(文件走 upload_* API),warn + 丢弃。
|
|
441
488
|
*/
|
|
442
489
|
export function formatOutboundSegments(payload: unknown): MilkyOutgoingSegment[] {
|
|
443
490
|
if (typeof payload === 'string') {
|
|
@@ -500,25 +547,20 @@ export function formatOutboundSegments(payload: unknown): MilkyOutgoingSegment[]
|
|
|
500
547
|
break;
|
|
501
548
|
}
|
|
502
549
|
case 'image':
|
|
503
|
-
out.push({
|
|
504
|
-
type: 'image',
|
|
505
|
-
data: { uri: String(data.url ?? data.uri ?? '') },
|
|
506
|
-
});
|
|
507
|
-
break;
|
|
508
550
|
case 'record':
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
551
|
+
case 'audio':
|
|
552
|
+
case 'video': {
|
|
553
|
+
const media = normalizeMediaSegment(item);
|
|
554
|
+
if (media) out.push(media);
|
|
513
555
|
break;
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
});
|
|
556
|
+
}
|
|
557
|
+
case 'file':
|
|
558
|
+
// Milky 消息 API 无 file 段(文件走 upload_private/group_file),无法投递
|
|
559
|
+
logger.warn(formatCompact({
|
|
560
|
+
op: 'milky_outbound_media_dropped',
|
|
561
|
+
type,
|
|
562
|
+
reason: 'unsupported_segment',
|
|
563
|
+
}));
|
|
522
564
|
break;
|
|
523
565
|
default:
|
|
524
566
|
out.push({ type, data: data as Record<string, unknown> });
|