@zhin.js/adapter-milky 6.0.2 → 6.0.5

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,91 @@
1
1
  # @zhin.js/adapter-milky
2
2
 
3
+ ## 6.0.5
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [6f9c366]
8
+ - Updated dependencies [373a56b]
9
+ - Updated dependencies [0de46a8]
10
+ - Updated dependencies [c106ecc]
11
+ - Updated dependencies [ca92e03]
12
+ - Updated dependencies [b0f37ae]
13
+ - Updated dependencies [ba08a2f]
14
+ - Updated dependencies [b08f7fe]
15
+ - Updated dependencies [daffd4c]
16
+ - Updated dependencies [36c7400]
17
+ - Updated dependencies [a9fa72e]
18
+ - Updated dependencies [c8de3ef]
19
+ - Updated dependencies [60f0fc8]
20
+ - Updated dependencies [574c990]
21
+ - Updated dependencies [d096f16]
22
+ - Updated dependencies [3f29623]
23
+ - Updated dependencies [2916852]
24
+ - Updated dependencies [d047869]
25
+ - Updated dependencies [162fa34]
26
+ - Updated dependencies [e62561e]
27
+ - Updated dependencies [3eeeb46]
28
+ - Updated dependencies [85d0f82]
29
+ - Updated dependencies [61bfc1c]
30
+ - Updated dependencies [04bad47]
31
+ - Updated dependencies [e40b048]
32
+ - Updated dependencies [5a5b1bb]
33
+ - Updated dependencies [f1708c3]
34
+ - Updated dependencies [d254a81]
35
+ - Updated dependencies [7123c47]
36
+ - Updated dependencies [05befc1]
37
+ - Updated dependencies [0663b6a]
38
+ - Updated dependencies [f28f9b3]
39
+ - Updated dependencies [9f340f7]
40
+ - Updated dependencies [e53444f]
41
+ - Updated dependencies [5eedd26]
42
+ - Updated dependencies [92b0dd7]
43
+ - Updated dependencies [f919b6f]
44
+ - Updated dependencies [098e411]
45
+ - Updated dependencies [e1b7c01]
46
+ - Updated dependencies [9b94f87]
47
+ - Updated dependencies [a7df753]
48
+ - @zhin.js/agent@1.1.5
49
+ - @zhin.js/host-http@1.0.8
50
+ - @zhin.js/im-contract@1.0.3
51
+ - @zhin.js/adapter@1.1.7
52
+ - @zhin.js/plugin-runtime@1.1.5
53
+ - @zhin.js/command@1.0.9
54
+ - @zhin.js/core@1.5.4
55
+ - zhin.js@6.0.4
56
+
57
+ ## 6.0.4
58
+
59
+ ### Patch Changes
60
+
61
+ - f8c7a54: fix: im
62
+ - Updated dependencies [f8c7a54]
63
+ - @zhin.js/logger@1.0.76
64
+ - @zhin.js/host-http@1.0.7
65
+ - @zhin.js/adapter@1.1.6
66
+ - @zhin.js/agent@1.1.4
67
+ - @zhin.js/command@1.0.8
68
+ - @zhin.js/core@1.5.3
69
+ - @zhin.js/im-contract@1.0.2
70
+ - @zhin.js/plugin-runtime@1.1.4
71
+ - zhin.js@6.0.3
72
+
73
+ ## 6.0.3
74
+
75
+ ### Patch Changes
76
+
77
+ - Updated dependencies [afc0e66]
78
+ - Updated dependencies [2e41ad5]
79
+ - Updated dependencies [9f57124]
80
+ - @zhin.js/core@1.5.2
81
+ - @zhin.js/adapter@1.1.5
82
+ - @zhin.js/im-contract@1.0.1
83
+ - @zhin.js/plugin-runtime@1.1.3
84
+ - @zhin.js/command@1.0.7
85
+ - @zhin.js/host-http@1.0.6
86
+ - @zhin.js/agent@1.1.3
87
+ - zhin.js@6.0.2
88
+
3
89
  ## 6.0.2
4
90
 
5
91
  ### Patch Changes
package/README.md CHANGED
@@ -8,7 +8,7 @@ Zhin.js [Milky](https://milky.ntqqrev.org/) 协议适配器(Plugin Runtime)
8
8
  - 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
9
9
  - **正向 WebSocket**(`connection: ws`):应用连协议端 `ws(s)://baseUrl/event`
10
10
  - `access_token` 鉴权(Bearer + query)
11
- - 入站经 `messageGatewayToken`;出站 `send({ target, payload })` → HTTP `send_*_message`
11
+ - 入站经 `messageGatewayToken`;出站 `send({ conversation, payload })` → HTTP `send_*_message`
12
12
 
13
13
  ## 安装
14
14
 
@@ -23,8 +23,8 @@ pnpm add @zhin.js/adapter-milky
23
23
  - `@zhin.js/plugin-runtime` — `plugin.ts`(`definePlugin`)
24
24
  - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
25
25
 
26
- 入站:`gateway.receive({ adapter, target: "private:uid"|"group:gid", content, sender, metadata })`
27
- 出站:`send({ target, payload })` → HTTP `send_private_message` / `send_group_message`(payload 已由 gateway/core 渲染;无 segment-mapper)
26
+ 入站:`gateway.receive({ conversation: ConversationRef(kind: "private"|"group", id), message, content, sender, metadata })`
27
+ 出站:`send({ conversation, payload })` → HTTP `send_private_message` / `send_group_message`(payload 已由 gateway/core 渲染;无 segment-mapper)
28
28
 
29
29
  ## 最小配置
30
30
 
package/adapters/milky.js CHANGED
@@ -20,8 +20,8 @@ export default defineAdapter({
20
20
  const config = resolveMilkyConfig(context.config);
21
21
  const gateway = context.use(messageGatewayToken);
22
22
  // 注册到插件运行时状态(milky.endpoint list 的"运行中"数据源)
23
- context.use(milkyRuntimeStateToken).endpoints.set(config.name, {
24
- name: config.name,
23
+ context.use(milkyRuntimeStateToken).endpoints.set(config.id, {
24
+ id: config.id,
25
25
  mode: config.connection,
26
26
  });
27
27
  if (config.connection === 'webhook') {
package/adapters/milky.ts CHANGED
@@ -43,8 +43,8 @@ export default defineAdapter<MilkyAdapterConfig>({
43
43
  const config = resolveMilkyConfig(context.config);
44
44
  const gateway = context.use(messageGatewayToken);
45
45
  // 注册到插件运行时状态(milky.endpoint list 的"运行中"数据源)
46
- context.use(milkyRuntimeStateToken).endpoints.set(config.name, {
47
- name: config.name,
46
+ context.use(milkyRuntimeStateToken).endpoints.set(config.id, {
47
+ id: config.id,
48
48
  mode: config.connection,
49
49
  });
50
50
  if (config.connection === 'webhook') {
package/lib/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { buildSendAction, buildSseConnectOptions, buildWsConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatInboundMessageId, formatInboundTarget, formatOutboundMessageId, formatOutboundSegments, isMentioned, isMessageReceiveEvent, parseMessageReceiveData, parseMilkyMessageId, parseSendTarget, resolveMilkyConfig, senderNickname, type MilkyAdapterConfig, type MilkyApiClientOptions, type MilkyApiResponse, type MilkyConfigBase, type MilkyEndpointConfig, type MilkyEvent, type MilkyIncomingMessage, type MilkyIncomingSegment, type MilkyOutgoingSegment, type MilkySseConfig, type MilkyWebhookConfig, type MilkyWireSegment, type MilkyWsConfig, type MilkyWssConfig, type ParsedSendTarget, type ResolvedMilkyConfig, } from './protocol.js';
1
+ export { buildSendAction, buildSseConnectOptions, buildWsConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatOutboundSegments, isMentioned, isMessageReceiveEvent, milkyInboundConversation, parseMessageReceiveData, resolveMilkyConfig, senderNickname, type MilkyAdapterConfig, type MilkyApiClientOptions, type MilkyApiResponse, type MilkyConfigBase, type MilkyEndpointConfig, type MilkyEvent, type MilkyIncomingMessage, type MilkyIncomingSegment, type MilkyOutgoingSegment, type MilkySseConfig, type MilkyWebhookConfig, type MilkyWireSegment, type MilkyWsConfig, type MilkyWssConfig, type ResolvedMilkyConfig, } from './protocol.js';
2
2
  export { MilkySseEndpoint, MilkyWebhookEndpoint, MilkyWssEndpoint, MilkyWsEndpoint, consumeSseBuffer, openSseStream, readRequestBody, verifyMilkyAccessToken, type CreateMilkySseStream, type MilkySseEndpointOptions, type MilkyWebhookEndpointOptions, type MilkyWssEndpointOptions, type MilkyWsCreateOptions, type MilkyWsEndpointOptions, type MilkyWsSocket, } from './endpoint.js';
3
3
  export { getMilkyAgentDeps, registerMilkyAgentEndpoint, setMilkyAgentDeps, type MilkyAgentDeps, type MilkyAgentEndpoint, } from './milky-agent-deps.js';
package/lib/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { buildSendAction, buildSseConnectOptions, buildWsConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatInboundMessageId, formatInboundTarget, formatOutboundMessageId, formatOutboundSegments, isMentioned, isMessageReceiveEvent, parseMessageReceiveData, parseMilkyMessageId, parseSendTarget, resolveMilkyConfig, senderNickname, } from './protocol.js';
1
+ export { buildSendAction, buildSseConnectOptions, buildWsConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatOutboundSegments, isMentioned, isMessageReceiveEvent, milkyInboundConversation, parseMessageReceiveData, resolveMilkyConfig, senderNickname, } from './protocol.js';
2
2
  export { MilkySseEndpoint, MilkyWebhookEndpoint, MilkyWssEndpoint, MilkyWsEndpoint, consumeSseBuffer, openSseStream, readRequestBody, verifyMilkyAccessToken, } from './endpoint.js';
3
3
  export { getMilkyAgentDeps, registerMilkyAgentEndpoint, setMilkyAgentDeps, } from './milky-agent-deps.js';
@@ -16,9 +16,9 @@ export interface MilkyAgentEndpoint {
16
16
  recallMessage?(id: string): Promise<void>;
17
17
  }
18
18
  export interface MilkyAgentDeps {
19
- getEndpoint: (endpointId: string) => MilkyAgentEndpoint;
19
+ getEndpoint: (endpointKey: string) => MilkyAgentEndpoint;
20
20
  }
21
- export declare function registerMilkyAgentEndpoint(endpointId: string, endpoint: MilkyAgentEndpoint): () => void;
21
+ export declare function registerMilkyAgentEndpoint(endpointKey: string, endpoint: MilkyAgentEndpoint): () => void;
22
22
  /** Optional override used by tests / transitional callers. Pass `null` to clear. */
23
23
  export declare function setMilkyAgentDeps(deps: MilkyAgentDeps | null): void;
24
24
  export declare function getMilkyAgentDeps(): MilkyAgentDeps;
@@ -4,11 +4,11 @@
4
4
  */
5
5
  const endpoints = new Map();
6
6
  let override = null;
7
- export function registerMilkyAgentEndpoint(endpointId, endpoint) {
8
- endpoints.set(endpointId, endpoint);
7
+ export function registerMilkyAgentEndpoint(endpointKey, endpoint) {
8
+ endpoints.set(endpointKey, endpoint);
9
9
  return () => {
10
- if (endpoints.get(endpointId) === endpoint) {
11
- endpoints.delete(endpointId);
10
+ if (endpoints.get(endpointKey) === endpoint) {
11
+ endpoints.delete(endpointKey);
12
12
  }
13
13
  };
14
14
  }
@@ -20,10 +20,10 @@ export function getMilkyAgentDeps() {
20
20
  if (override)
21
21
  return override;
22
22
  return {
23
- getEndpoint(endpointId) {
24
- const registered = endpoints.get(endpointId);
23
+ getEndpoint(endpointKey) {
24
+ const registered = endpoints.get(endpointKey);
25
25
  if (!registered)
26
- throw new Error(`Endpoint ${endpointId} 不存在`);
26
+ throw new Error(`Endpoint ${endpointKey} 不存在`);
27
27
  return registered;
28
28
  },
29
29
  };
package/lib/protocol.d.ts CHANGED
@@ -4,11 +4,12 @@
4
4
  * Spec: https://milky.ntqqrev.org/
5
5
  */
6
6
  import type { Segment } from '@zhin.js/core/runtime';
7
+ import type { ConversationRef } from '@zhin.js/im-contract';
7
8
  /** Transitional legacy endpoint row (`endpoints[]` with `context: milky`). */
8
9
  export interface MilkyLegacyEndpointRow {
9
10
  readonly context?: string;
10
11
  readonly connection?: 'ws' | 'sse' | 'webhook' | 'wss';
11
- readonly name?: string;
12
+ readonly id?: string;
12
13
  readonly baseUrl?: string;
13
14
  readonly access_token?: string;
14
15
  readonly path?: string;
@@ -18,7 +19,7 @@ export interface MilkyLegacyEndpointRow {
18
19
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
19
20
  export interface MilkyAdapterConfig {
20
21
  readonly connection?: 'ws' | 'sse' | 'webhook' | 'wss';
21
- readonly name?: string;
22
+ readonly id?: string;
22
23
  readonly baseUrl?: string;
23
24
  readonly access_token?: string;
24
25
  readonly path?: string;
@@ -29,7 +30,7 @@ export interface MilkyAdapterConfig {
29
30
  }
30
31
  export interface MilkyConfigBase {
31
32
  readonly context: 'milky';
32
- readonly name: string;
33
+ readonly id: string;
33
34
  readonly baseUrl: string;
34
35
  readonly access_token?: string;
35
36
  }
@@ -103,10 +104,6 @@ export interface MilkyOutgoingSegment {
103
104
  type: string;
104
105
  data: Record<string, unknown>;
105
106
  }
106
- export interface ParsedSendTarget {
107
- readonly message_type: 'private' | 'group';
108
- readonly id: string;
109
- }
110
107
  export interface MilkyApiClientOptions {
111
108
  readonly baseUrl: string;
112
109
  readonly access_token?: string;
@@ -122,8 +119,11 @@ export declare function parseMessageReceiveData(event: MilkyEvent): MilkyIncomin
122
119
  export declare function isMessageReceiveEvent(event: MilkyEvent): event is MilkyEvent & {
123
120
  data: MilkyIncomingMessage;
124
121
  };
125
- /** Gateway reply target:`private:uid` / `group:gid` */
126
- export declare function formatInboundTarget(data: MilkyIncomingMessage): string;
122
+ /**
123
+ * 入站归一化 ConversationRef:`friend`(私聊)→ `private`;`group` → `group`;
124
+ * `temp`(群临时会话)→ 群容器内的 `private` 会话(parent = 来源群)。
125
+ */
126
+ export declare function milkyInboundConversation(endpointKey: string, data: MilkyIncomingMessage): ConversationRef;
127
127
  export declare function formatInboundContent(data: MilkyIncomingMessage): string;
128
128
  /**
129
129
  * 入站消息段 → canonical Segment[](与 formatInboundContent 纯文本视图同源双轨)。
@@ -139,7 +139,6 @@ export declare function senderNickname(data: MilkyIncomingMessage): string | und
139
139
  /** 消息段中含 @ 本机(mention 段 user_id 等于协议端上报的 self_id)。 */
140
140
  export declare function isMentioned(data: MilkyIncomingMessage, selfId: number | undefined): boolean;
141
141
  export declare function formatInboundMessageId(data: MilkyIncomingMessage): string;
142
- export declare function parseSendTarget(target: string): ParsedSendTarget;
143
142
  /**
144
143
  * Wire-encode an already-rendered outbound payload into Milky outgoing segments.
145
144
  * Segment canonicalization is intentionally not done here.
@@ -147,12 +146,15 @@ export declare function parseSendTarget(target: string): ParsedSendTarget;
147
146
  * file 段 Milky 消息 API 无对应 wire 段(文件走 upload_* API),warn + 丢弃。
148
147
  */
149
148
  export declare function formatOutboundSegments(payload: unknown): MilkyOutgoingSegment[];
150
- export declare function buildSendAction(target: string, message: MilkyOutgoingSegment[]): {
149
+ export declare function buildSendAction(conversation: ConversationRef, message: MilkyOutgoingSegment[]): {
151
150
  action: string;
152
151
  params: Record<string, unknown>;
153
152
  };
154
- /** message_seq result → gateway message id */
155
- export declare function formatOutboundMessageId(target: string, messageSeq: number | undefined): string;
153
+ /**
154
+ * message_seq result gateway message id(`scene:peer:seq` 复合格式,
155
+ * 与 formatInboundMessageId 同源,仅供 recall 链在本端点边界内解析)。
156
+ */
157
+ export declare function formatOutboundMessageId(conversation: ConversationRef, messageSeq: number | undefined): string;
156
158
  export declare function parseMilkyMessageId(msgId: string): {
157
159
  message_scene: 'friend' | 'group' | 'temp';
158
160
  peer_id: number;
package/lib/protocol.js CHANGED
@@ -14,8 +14,8 @@ function normalizeConnection(connection) {
14
14
  export function resolveMilkyConfig(config = {}) {
15
15
  const entry = config.endpoints?.find((item) => item.context === 'milky');
16
16
  const connection = normalizeConnection(config.connection ?? entry?.connection);
17
- const name = (typeof config.name === 'string' && config.name)
18
- || (typeof entry?.name === 'string' && entry.name)
17
+ const id = (typeof config.id === 'string' && config.id)
18
+ || (typeof entry?.id === 'string' && entry.id)
19
19
  || process.env.MILKY_BOT_NAME
20
20
  || 'milky-bot';
21
21
  const baseUrl = config.baseUrl ?? entry?.baseUrl;
@@ -25,7 +25,7 @@ export function resolveMilkyConfig(config = {}) {
25
25
  const access_token = config.access_token ?? entry?.access_token;
26
26
  const base = {
27
27
  context: 'milky',
28
- name,
28
+ id,
29
29
  baseUrl,
30
30
  access_token,
31
31
  };
@@ -125,10 +125,19 @@ export function parseMessageReceiveData(event) {
125
125
  export function isMessageReceiveEvent(event) {
126
126
  return parseMessageReceiveData(event) != null;
127
127
  }
128
- /** Gateway reply target:`private:uid` / `group:gid` */
129
- export function formatInboundTarget(data) {
130
- const isGroup = data.message_scene === 'group';
131
- return `${isGroup ? 'group' : 'private'}:${data.peer_id}`;
128
+ /**
129
+ * 入站归一化 → ConversationRef:`friend`(私聊)→ `private`;`group` → `group`;
130
+ * `temp`(群临时会话)→ 群容器内的 `private` 会话(parent = 来源群)。
131
+ */
132
+ export function milkyInboundConversation(endpointKey, data) {
133
+ return {
134
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
135
+ kind: data.message_scene === 'group' ? 'group' : 'private',
136
+ id: String(data.peer_id),
137
+ ...(data.message_scene === 'temp' && data.group
138
+ ? { parent: { kind: 'group', id: String(data.group.group_id) } }
139
+ : {}),
140
+ };
132
141
  }
133
142
  export function formatInboundContent(data) {
134
143
  return data.segments.map((seg) => {
@@ -290,19 +299,6 @@ export function isMentioned(data, selfId) {
290
299
  export function formatInboundMessageId(data) {
291
300
  return `${data.message_scene}:${data.peer_id}:${data.message_seq}`;
292
301
  }
293
- export function parseSendTarget(target) {
294
- const sep = target.indexOf(':');
295
- if (sep <= 0) {
296
- return { message_type: 'private', id: target };
297
- }
298
- const head = target.slice(0, sep);
299
- const rest = target.slice(sep + 1);
300
- if (head === 'group')
301
- return { message_type: 'group', id: rest };
302
- if (head === 'private')
303
- return { message_type: 'private', id: rest };
304
- return { message_type: 'private', id: target };
305
- }
306
302
  /**
307
303
  * 解析 image/audio/video 段的投递 uri(canonical MediaRef 唯一来源):
308
304
  * - kind=url → http(s):// 直发;
@@ -432,13 +428,12 @@ export function formatOutboundSegments(payload) {
432
428
  }
433
429
  return out.length ? out : [{ type: 'text', data: { text: '' } }];
434
430
  }
435
- export function buildSendAction(target, message) {
436
- const parsed = parseSendTarget(target);
437
- if (parsed.message_type === 'group') {
431
+ export function buildSendAction(conversation, message) {
432
+ if (conversation.kind === 'group') {
438
433
  return {
439
434
  action: 'send_group_message',
440
435
  params: {
441
- group_id: parseInt(parsed.id, 10),
436
+ group_id: parseInt(conversation.id, 10),
442
437
  message,
443
438
  },
444
439
  };
@@ -446,18 +441,20 @@ export function buildSendAction(target, message) {
446
441
  return {
447
442
  action: 'send_private_message',
448
443
  params: {
449
- user_id: parseInt(parsed.id, 10),
444
+ user_id: parseInt(conversation.id, 10),
450
445
  message,
451
446
  },
452
447
  };
453
448
  }
454
- /** message_seq result → gateway message id */
455
- export function formatOutboundMessageId(target, messageSeq) {
449
+ /**
450
+ * message_seq result → gateway message id(`scene:peer:seq` 复合格式,
451
+ * 与 formatInboundMessageId 同源,仅供 recall 链在本端点边界内解析)。
452
+ */
453
+ export function formatOutboundMessageId(conversation, messageSeq) {
456
454
  if (messageSeq == null)
457
455
  return '';
458
- const parsed = parseSendTarget(target);
459
- const scene = parsed.message_type === 'group' ? 'group' : 'friend';
460
- return `${scene}:${parsed.id}:${messageSeq}`;
456
+ const scene = conversation.kind === 'group' ? 'group' : 'friend';
457
+ return `${scene}:${conversation.id}:${messageSeq}`;
461
458
  }
462
459
  export function parseMilkyMessageId(msgId) {
463
460
  const parts = msgId.split(':');
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Milky SSE client endpoint — GET text/event-stream on /event.
3
3
  */
4
- import { type EndpointInstance, type EndpointManagement } from '@zhin.js/adapter';
4
+ import { type EndpointInstance, type EndpointManagement, type EndpointSendRequest } from '@zhin.js/adapter';
5
5
  import type { MessageGateway } from '@zhin.js/core/runtime';
6
6
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
7
7
  import { callApi, type MilkyEvent, type MilkySseConfig } from './protocol.js';
@@ -28,10 +28,7 @@ export declare class MilkySseEndpoint implements EndpointInstance {
28
28
  open(): void;
29
29
  close(): void;
30
30
  stop(): Promise<void>;
31
- send({ target, payload }: {
32
- readonly target: string;
33
- readonly payload: unknown;
34
- }): Promise<string>;
31
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
35
32
  callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
36
33
  recallMessage(id: string): Promise<void>;
37
34
  kickMember(groupId: number, userId: number, rejectAddRequest?: boolean): Promise<boolean>;
@@ -2,13 +2,13 @@
2
2
  * Milky SSE client endpoint — GET text/event-stream on /event.
3
3
  */
4
4
  import { createEndpointLifecycle, } from '@zhin.js/adapter';
5
- import { formatCompact, getLogger } from '@zhin.js/logger';
5
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
6
6
  import { createMilkyEndpointManagement } from './endpoint-management.js';
7
7
  import { registerMilkyAgentEndpoint } from './milky-agent-deps.js';
8
- import { buildSendAction, buildSseConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatInboundMessageId, formatInboundSegments, formatInboundTarget, formatOutboundMessageId, formatOutboundSegments, isMentioned, parseMessageReceiveData, parseMilkyMessageId, senderNickname, } from './protocol.js';
8
+ import { buildSendAction, buildSseConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatInboundMessageId, formatInboundSegments, formatOutboundMessageId, formatOutboundSegments, isMentioned, milkyInboundConversation, parseMessageReceiveData, parseMilkyMessageId, senderNickname, } from './protocol.js';
9
9
  import { openSseStream } from './sse-client.js';
10
- const logger = getLogger('milky');
11
10
  export class MilkySseEndpoint {
11
+ #logger;
12
12
  #options;
13
13
  #callApi;
14
14
  management = createMilkyEndpointManagement(this);
@@ -17,10 +17,11 @@ export class MilkySseEndpoint {
17
17
  #open = false;
18
18
  #unregisterAgent;
19
19
  constructor(options) {
20
+ this.#logger = getAdapterLogger('milky', options.config.id);
20
21
  this.#options = options;
21
22
  this.#callApi = options.callApi ?? callApi;
22
23
  this.#lifecycle = createEndpointLifecycle({
23
- name: options.config.name,
24
+ name: options.config.id,
24
25
  // reconnect_interval 旧语义为固定间隔:multiplier 1 + 无 jitter + 不封顶
25
26
  reconnect: {
26
27
  initialIntervalMs: options.config.reconnect_interval,
@@ -33,7 +34,7 @@ export class MilkySseEndpoint {
33
34
  async start() {
34
35
  if (this.#lifecycle.started)
35
36
  return;
36
- this.#unregisterAgent = registerMilkyAgentEndpoint(this.#options.config.name, this);
37
+ this.#unregisterAgent = registerMilkyAgentEndpoint(this.#options.config.id, this);
37
38
  try {
38
39
  await this.#lifecycle.start((handle) => this.#connect(handle));
39
40
  }
@@ -58,15 +59,15 @@ export class MilkySseEndpoint {
58
59
  this.#stream?.close();
59
60
  this.#stream = undefined;
60
61
  }
61
- async send({ target, payload }) {
62
+ async send({ conversation, payload }) {
62
63
  const message = formatOutboundSegments(payload);
63
- const { action, params } = buildSendAction(target, message);
64
+ const { action, params } = buildSendAction(conversation, message);
64
65
  const data = await this.callApi(action, params);
65
- const messageId = formatOutboundMessageId(target, data?.message_seq);
66
- logger.debug(formatCompact({
66
+ const messageId = formatOutboundMessageId(conversation, data?.message_seq);
67
+ this.#logger.debug(formatCompact({
67
68
  op: 'milky_send',
68
- endpoint: this.#options.config.name,
69
- target,
69
+ endpoint: this.#options.config.id,
70
+ target: `${conversation.kind}:${conversation.id}`,
70
71
  messageId,
71
72
  mode: 'sse',
72
73
  }));
@@ -159,33 +160,32 @@ export class MilkySseEndpoint {
159
160
  };
160
161
  }
161
162
  #admitMessage(data, event) {
162
- const target = formatInboundTarget(data);
163
+ const conversation = milkyInboundConversation(String(this.#options.id), data);
164
+ const target = `${conversation.kind}:${conversation.id}`;
163
165
  const content = formatInboundContent(data);
164
166
  const segments = formatInboundSegments(data);
165
167
  const audioUrl = extractInboundAudioUrl(data);
166
168
  const nickname = senderNickname(data);
167
169
  const mentioned = isMentioned(data, event.self_id);
168
170
  void this.#options.gateway.receive({
169
- adapter: this.#options.id,
170
- target,
171
+ conversation,
172
+ message: { conversation, id: formatInboundMessageId(data) },
171
173
  content,
172
174
  segments,
173
- sender: String(data.sender_id),
174
- id: formatInboundMessageId(data),
175
+ sender: { id: String(data.sender_id), name: nickname },
176
+ endpointId: this.#options.config.id,
177
+ ...(mentioned ? { mentioned: true } : {}),
175
178
  metadata: Object.freeze({
176
179
  message_scene: data.message_scene,
177
180
  peer_id: String(data.peer_id),
178
181
  sender_id: String(data.sender_id),
179
182
  message_seq: data.message_seq,
180
- endpoint: this.#options.config.name,
181
183
  time: data.time ?? event.time,
182
184
  self_id: event.self_id != null ? String(event.self_id) : undefined,
183
185
  ...(nickname ? { nickname } : {}),
184
- ...(mentioned ? { mentioned: true } : {}),
185
- ...(audioUrl ? { audio_url: audioUrl } : {}),
186
186
  }),
187
187
  }).catch((err) => {
188
- logger.warn(formatCompact({
188
+ this.#logger.warn(formatCompact({
189
189
  op: 'milky_gateway_receive_failed',
190
190
  target,
191
191
  error: err instanceof Error ? err.message : String(err),
@@ -204,8 +204,8 @@ export class MilkySseEndpoint {
204
204
  if (settled)
205
205
  return;
206
206
  settled = true;
207
- logger.debug(formatCompact({
208
- endpoint: this.#options.config.name,
207
+ this.#logger.debug(formatCompact({
208
+ endpoint: this.#options.config.id,
209
209
  mode: 'sse',
210
210
  url: safeUrl,
211
211
  }));
@@ -213,9 +213,9 @@ export class MilkySseEndpoint {
213
213
  },
214
214
  onMessage: (data) => this.#onMessage(data),
215
215
  onError: (error) => {
216
- logger.warn(formatCompact({
216
+ this.#logger.warn(formatCompact({
217
217
  op: 'sse_error',
218
- endpoint: this.#options.config.name,
218
+ endpoint: this.#options.config.id,
219
219
  ok: false,
220
220
  error: error.message,
221
221
  }));
@@ -237,9 +237,8 @@ export class MilkySseEndpoint {
237
237
  void stream.closed.then(() => {
238
238
  // stop-during-connect 竞态由基座静默 settle(主动停止不算失败),此处仅对运行期断开告警
239
239
  if (this.#lifecycle.state !== 'stopped') {
240
- logger.warn(formatCompact({
240
+ this.#logger.warn(formatCompact({
241
241
  op: 'disconnect',
242
- endpoint: this.#options.config.name,
243
242
  mode: 'sse',
244
243
  reconnect_ms: this.#options.config.reconnect_interval,
245
244
  }));
@@ -259,9 +258,9 @@ export class MilkySseEndpoint {
259
258
  this.admit(event);
260
259
  }
261
260
  catch (error) {
262
- logger.warn(formatCompact({
261
+ this.#logger.warn(formatCompact({
263
262
  op: 'milky_parse_failed',
264
- endpoint: this.#options.config.name,
263
+ endpoint: this.#options.config.id,
265
264
  mode: 'sse',
266
265
  error: error instanceof Error ? error.message : String(error),
267
266
  }));
@@ -1,4 +1,4 @@
1
- import type { EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
1
+ import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
2
2
  import type { MessageGateway } from '@zhin.js/core/runtime';
3
3
  import type { HttpHost } from '@zhin.js/host-http';
4
4
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
@@ -18,10 +18,7 @@ export declare class MilkyWebhookEndpoint implements EndpointInstance {
18
18
  open(): void;
19
19
  close(): void;
20
20
  stop(): Promise<void>;
21
- send({ target, payload }: {
22
- readonly target: string;
23
- readonly payload: unknown;
24
- }): Promise<string>;
21
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
25
22
  callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
26
23
  recallMessage(id: string): Promise<void>;
27
24
  kickMember(groupId: number, userId: number, rejectAddRequest?: boolean): Promise<boolean>;