@zhin.js/adapter-satori 5.0.0 → 5.0.2

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,32 @@
1
1
  # @zhin.js/adapter-satori
2
2
 
3
+ ## 5.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [afc0e66]
8
+ - Updated dependencies [2e41ad5]
9
+ - Updated dependencies [9f57124]
10
+ - @zhin.js/core@1.5.2
11
+ - @zhin.js/adapter@1.1.5
12
+ - @zhin.js/im-contract@1.0.1
13
+ - @zhin.js/plugin-runtime@1.1.3
14
+ - @zhin.js/command@1.0.7
15
+ - @zhin.js/host-http@1.0.6
16
+ - zhin.js@6.0.2
17
+
18
+ ## 5.0.1
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [c8f4d45]
23
+ - @zhin.js/plugin-runtime@1.1.2
24
+ - @zhin.js/host-http@1.0.5
25
+ - @zhin.js/adapter@1.1.4
26
+ - @zhin.js/command@1.0.6
27
+ - @zhin.js/core@1.5.1
28
+ - zhin.js@6.0.1
29
+
3
30
  ## 5.0.0
4
31
 
5
32
  ### Patch Changes
package/README.md CHANGED
@@ -27,8 +27,8 @@ pnpm add @zhin.js/adapter-satori
27
27
  - `@zhin.js/host-http` — Webhook 模式需 `httpHostToken` 注册 POST 路由
28
28
  - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`(`baseUrl` / `token` / …)
29
29
 
30
- 入站:`gateway.receive({ adapter, target: channelId, content: text, sender, id, metadata })`
31
- 出站:`send({ target: channelId, payload })` → Satori `message.create`(payload 已由 gateway/core 渲染;无 segment-mapper)
30
+ 入站:`gateway.receive({ conversation, message, content, sender, metadata })`(`conversation` 为 ConversationRef:DIRECT 频道 → kind `private`,其余 → kind `group`、所属 guild 进 `parent`)
31
+ 出站:`send({ conversation, payload })` → Satori `message.create`(`channel_id = conversation.id`;payload 已由 gateway/core 渲染;无 segment-mapper)
32
32
 
33
33
  入站 `metadata.mentioned`:消息 content 中 `<at id="…"/>` 元素的 id 等于登录 selfId(READY/事件 `login.user.id`)时置 `true`。
34
34
 
package/lib/endpoint.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * SatoriEndpoint — WebSocket and webhook lifecycle, outbound, admit.
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 { HttpHost } from '@zhin.js/host-http';
7
7
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
@@ -23,10 +23,7 @@ export declare class SatoriWsEndpoint implements EndpointInstance {
23
23
  open(): void;
24
24
  close(): void;
25
25
  stop(): Promise<void>;
26
- send({ target, payload }: {
27
- readonly target: string;
28
- readonly payload: unknown;
29
- }): Promise<string>;
26
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
30
27
  /** Test / internal: admit a gateway event when the endpoint is open. */
31
28
  admit(body: SatoriEventBody): void;
32
29
  recall(id: string): Promise<void>;
@@ -51,10 +48,7 @@ export declare class SatoriWebhookEndpoint implements EndpointInstance {
51
48
  open(): void;
52
49
  close(): void;
53
50
  stop(): Promise<void>;
54
- send({ target, payload }: {
55
- readonly target: string;
56
- readonly payload: unknown;
57
- }): Promise<string>;
51
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
58
52
  admit(body: SatoriEventBody): void;
59
53
  recall(id: string): Promise<void>;
60
54
  /** Test helper: inject login without a live webhook push. */
package/lib/endpoint.js CHANGED
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { createEndpointLifecycle, } from '@zhin.js/adapter';
5
5
  import { formatCompact, getLogger } from '@zhin.js/logger';
6
- import { SatoriOpcode, buildWsUrl, callSatoriApi, extractCreatedMessageId, formatInboundContent, formatMessageId, formatSatoriOutbound, isMessageEvent, isSelfMentioned, parseMessageRef, resolveInboundSender, resolveInboundTarget, } from './protocol.js';
6
+ import { SatoriOpcode, buildWsUrl, callSatoriApi, extractCreatedMessageId, formatInboundContent, formatMessageId, formatSatoriOutbound, isMessageEvent, isSelfMentioned, parseMessageRef, resolveInboundSender, satoriInboundConversation, } from './protocol.js';
7
7
  import { registerSatoriWebhookRoutes } from './webhook.js';
8
8
  import { WS_OPEN, defaultCreateWebSocket, } from './ws.js';
9
9
  const logger = getLogger('satori');
@@ -64,20 +64,20 @@ export class SatoriWsEndpoint {
64
64
  await this.#lifecycle.stop();
65
65
  this.#ws = null;
66
66
  }
67
- async send({ target, payload }) {
67
+ async send({ conversation, payload }) {
68
68
  const content = formatSatoriOutbound(payload);
69
69
  const result = await this.#api('message', 'create', {
70
- channel_id: target,
70
+ channel_id: conversation.id,
71
71
  content,
72
72
  });
73
73
  const msgId = extractCreatedMessageId(result);
74
74
  logger.debug(formatCompact({
75
75
  op: 'satori_send',
76
76
  endpoint: this.#options.config.name,
77
- target,
77
+ channel: conversation.id,
78
78
  messageId: msgId || undefined,
79
79
  }));
80
- return msgId ? formatMessageId(target, msgId) : '';
80
+ return msgId ? formatMessageId(conversation.id, msgId) : '';
81
81
  }
82
82
  /** Test / internal: admit a gateway event when the endpoint is open. */
83
83
  admit(body) {
@@ -87,18 +87,16 @@ export class SatoriWsEndpoint {
87
87
  this.#login = body.login;
88
88
  if (!isMessageEvent(body))
89
89
  return;
90
- const target = resolveInboundTarget(body);
90
+ const conversation = satoriInboundConversation(String(this.#options.id), body);
91
91
  const content = formatInboundContent(body);
92
92
  const sender = resolveInboundSender(body);
93
- const messageId = formatMessageId(target, body.message.id);
94
93
  const selfId = this.#login?.user?.id ?? body.login?.user?.id;
95
94
  const mentioned = isSelfMentioned(body, selfId);
96
95
  void this.#options.gateway.receive({
97
- adapter: this.#options.id,
98
- target,
96
+ conversation,
97
+ message: { conversation, id: body.message.id },
99
98
  content,
100
99
  sender,
101
- id: messageId,
102
100
  metadata: Object.freeze({
103
101
  type: body.type,
104
102
  channelType: isPrivateChannelType(body) ? 'private' : 'group',
@@ -110,7 +108,7 @@ export class SatoriWsEndpoint {
110
108
  }).catch((err) => {
111
109
  logger.warn(formatCompact({
112
110
  op: 'satori_gateway_receive_failed',
113
- target,
111
+ channel: conversation.id,
114
112
  error: err instanceof Error ? err.message : String(err),
115
113
  }));
116
114
  });
@@ -295,20 +293,20 @@ export class SatoriWebhookEndpoint {
295
293
  endpoint: this.#options.config.name,
296
294
  }));
297
295
  }
298
- async send({ target, payload }) {
296
+ async send({ conversation, payload }) {
299
297
  const content = formatSatoriOutbound(payload);
300
298
  const result = await this.#api('message', 'create', {
301
- channel_id: target,
299
+ channel_id: conversation.id,
302
300
  content,
303
301
  });
304
302
  const msgId = extractCreatedMessageId(result);
305
303
  logger.debug(formatCompact({
306
304
  op: 'satori_send',
307
305
  endpoint: this.#options.config.name,
308
- target,
306
+ channel: conversation.id,
309
307
  messageId: msgId || undefined,
310
308
  }));
311
- return msgId ? formatMessageId(target, msgId) : '';
309
+ return msgId ? formatMessageId(conversation.id, msgId) : '';
312
310
  }
313
311
  admit(body) {
314
312
  if (!this.#open)
@@ -317,18 +315,16 @@ export class SatoriWebhookEndpoint {
317
315
  this.#login = body.login;
318
316
  if (!isMessageEvent(body))
319
317
  return;
320
- const target = resolveInboundTarget(body);
318
+ const conversation = satoriInboundConversation(String(this.#options.id), body);
321
319
  const content = formatInboundContent(body);
322
320
  const sender = resolveInboundSender(body);
323
- const messageId = formatMessageId(target, body.message.id);
324
321
  const selfId = this.#login?.user?.id ?? body.login?.user?.id;
325
322
  const mentioned = isSelfMentioned(body, selfId);
326
323
  void this.#options.gateway.receive({
327
- adapter: this.#options.id,
328
- target,
324
+ conversation,
325
+ message: { conversation, id: body.message.id },
329
326
  content,
330
327
  sender,
331
- id: messageId,
332
328
  metadata: Object.freeze({
333
329
  type: body.type,
334
330
  channelType: isPrivateChannelType(body) ? 'private' : 'group',
@@ -340,7 +336,7 @@ export class SatoriWebhookEndpoint {
340
336
  }).catch((err) => {
341
337
  logger.warn(formatCompact({
342
338
  op: 'satori_gateway_receive_failed',
343
- target,
339
+ channel: conversation.id,
344
340
  error: err instanceof Error ? err.message : String(err),
345
341
  }));
346
342
  });
package/lib/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { SatoriOpcode, buildWsUrl, callSatoriApi, extractCreatedMessageId, formatInboundContent, formatMessageId, formatSatoriOutbound, isMessageEvent, isPrivateChannel, parseMessageRef, resolveInboundSender, resolveInboundTarget, resolveSatoriConfig, type ResolvedSatoriWebhookConfig, type ResolvedSatoriWsConfig, type SatoriAdapterConfig, type SatoriApiOptions, type SatoriChannel, type SatoriEventBody, type SatoriLogin, type SatoriMessage, type SatoriSignal, type SatoriUser, type SatoriWireSegment, } from './protocol.js';
1
+ export { SatoriOpcode, buildWsUrl, callSatoriApi, extractCreatedMessageId, formatInboundContent, formatSatoriOutbound, isMessageEvent, isPrivateChannel, resolveInboundSender, resolveSatoriConfig, satoriInboundConversation, type ResolvedSatoriWebhookConfig, type ResolvedSatoriWsConfig, type SatoriAdapterConfig, type SatoriApiOptions, type SatoriChannel, type SatoriEventBody, type SatoriLogin, type SatoriMessage, type SatoriSignal, type SatoriUser, type SatoriWireSegment, } from './protocol.js';
2
2
  export { SatoriWebhookEndpoint, SatoriWsEndpoint, type CreateSatoriWebSocket, type SatoriApiCaller, type SatoriWebhookEndpointOptions, type SatoriWsEndpointOptions, type SatoriWsSocket, } from './endpoint.js';
3
3
  export { handleSatoriWebhookRequest, readRequestBody, registerSatoriWebhookRoutes, resolveSatoriOpcode, verifySatoriToken, type SatoriWebhookHandler, } from './webhook.js';
4
4
  export { WS_OPEN, defaultCreateWebSocket, } from './ws.js';
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { SatoriOpcode, buildWsUrl, callSatoriApi, extractCreatedMessageId, formatInboundContent, formatMessageId, formatSatoriOutbound, isMessageEvent, isPrivateChannel, parseMessageRef, resolveInboundSender, resolveInboundTarget, resolveSatoriConfig, } from './protocol.js';
1
+ export { SatoriOpcode, buildWsUrl, callSatoriApi, extractCreatedMessageId, formatInboundContent, formatSatoriOutbound, isMessageEvent, isPrivateChannel, resolveInboundSender, resolveSatoriConfig, satoriInboundConversation, } from './protocol.js';
2
2
  export { SatoriWebhookEndpoint, SatoriWsEndpoint, } from './endpoint.js';
3
3
  export { handleSatoriWebhookRequest, readRequestBody, registerSatoriWebhookRoutes, resolveSatoriOpcode, verifySatoriToken, } from './webhook.js';
4
4
  export { WS_OPEN, defaultCreateWebSocket, } from './ws.js';
package/lib/protocol.d.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * Canonicalization is owned by gateway/core before endpoint.send.
4
4
  * Spec: https://satori.chat/zh-CN/protocol/overview.html
5
5
  */
6
+ import type { ConversationRef } from '@zhin.js/im-contract';
6
7
  /** Opcode:EVENT=0, PING=1, PONG=2, IDENTIFY=3, READY=4, META=5 */
7
8
  export declare const SatoriOpcode: {
8
9
  readonly EVENT: 0;
@@ -124,9 +125,13 @@ export declare function callSatoriApi<T = unknown>(options: SatoriApiOptions, re
124
125
  export declare function formatInboundContent(body: SatoriEventBody & {
125
126
  message: SatoriMessage;
126
127
  }): string;
127
- export declare function resolveInboundTarget(body: SatoriEventBody & {
128
+ /**
129
+ * 入站归一化 → ConversationRef:Channel.type 1 (DIRECT) → kind 'private';
130
+ * 其余频道消息 → kind 'group',所属 guild 容器进 `parent`(kind 'channel')。
131
+ */
132
+ export declare function satoriInboundConversation(endpointId: string, body: SatoriEventBody & {
128
133
  message: SatoriMessage;
129
- }): string;
134
+ }): ConversationRef;
130
135
  export declare function resolveInboundSender(body: SatoriEventBody & {
131
136
  message: SatoriMessage;
132
137
  }): string;
package/lib/protocol.js CHANGED
@@ -113,9 +113,21 @@ export function formatInboundContent(body) {
113
113
  const content = body.message.content ?? '';
114
114
  return typeof content === 'string' ? content : String(content);
115
115
  }
116
- export function resolveInboundTarget(body) {
116
+ /**
117
+ * 入站归一化 → ConversationRef:Channel.type 1 (DIRECT) → kind 'private';
118
+ * 其余频道消息 → kind 'group',所属 guild 容器进 `parent`(kind 'channel')。
119
+ */
120
+ export function satoriInboundConversation(endpointId, body) {
117
121
  const channel = body.channel ?? body.message.channel;
118
- return channel?.id ?? '';
122
+ const kind = isPrivateChannel(channel) ? 'private' : 'group';
123
+ return {
124
+ endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
125
+ kind,
126
+ id: channel?.id ?? '',
127
+ ...(kind === 'group' && body.guild?.id
128
+ ? { parent: { kind: 'channel', id: body.guild.id } }
129
+ : {}),
130
+ };
119
131
  }
120
132
  export function resolveInboundSender(body) {
121
133
  const user = body.user ?? body.message.user ?? body.message.member?.user;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-satori",
3
- "version": "5.0.0",
3
+ "version": "5.0.2",
4
4
  "description": "Zhin.js Satori adapter for Plugin Runtime (WebSocket client)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -39,25 +39,26 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "ws": "^8.21.1",
42
- "@zhin.js/adapter": "1.1.3",
43
- "@zhin.js/command": "1.0.5",
44
- "@zhin.js/core": "1.5.0",
45
- "@zhin.js/host-http": "1.0.4",
42
+ "@zhin.js/adapter": "1.1.5",
43
+ "@zhin.js/command": "1.0.7",
44
+ "@zhin.js/core": "1.5.2",
45
+ "@zhin.js/host-http": "1.0.6",
46
+ "@zhin.js/im-contract": "1.0.1",
46
47
  "@zhin.js/logger": "1.0.75",
47
- "@zhin.js/plugin-runtime": "1.1.1"
48
+ "@zhin.js/plugin-runtime": "1.1.3"
48
49
  },
49
50
  "devDependencies": {
50
51
  "@types/node": "^26.1.2",
51
52
  "@types/ws": "^8.18.1",
52
53
  "typescript": "^6.0.3",
53
54
  "vitest": "^4.1.10",
54
- "@zhin.js/host-http": "1.0.4"
55
+ "@zhin.js/host-http": "1.0.6"
55
56
  },
56
57
  "peerDependencies": {
57
- "@zhin.js/adapter": "1.1.3",
58
- "@zhin.js/core": "1.5.0",
59
- "@zhin.js/plugin-runtime": "1.1.1",
60
- "zhin.js": "6.0.0"
58
+ "@zhin.js/adapter": "1.1.5",
59
+ "@zhin.js/core": "1.5.2",
60
+ "@zhin.js/plugin-runtime": "1.1.3",
61
+ "zhin.js": "6.0.2"
61
62
  },
62
63
  "peerDependenciesMeta": {
63
64
  "zhin.js": {
package/src/endpoint.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  type EndpointInstance,
10
10
  type EndpointLifecycle,
11
11
  type EndpointManagement,
12
+ type EndpointSendRequest,
12
13
  } from '@zhin.js/adapter';
13
14
  import type { MessageGateway } from '@zhin.js/core/runtime';
14
15
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
@@ -26,7 +27,7 @@ import {
26
27
  isSelfMentioned,
27
28
  parseMessageRef,
28
29
  resolveInboundSender,
29
- resolveInboundTarget,
30
+ satoriInboundConversation,
30
31
  type ResolvedSatoriWebhookConfig,
31
32
  type ResolvedSatoriWsConfig,
32
33
  type SatoriApiOptions,
@@ -117,20 +118,20 @@ export class SatoriWsEndpoint implements EndpointInstance {
117
118
  this.#ws = null;
118
119
  }
119
120
 
120
- async send({ target, payload }: { readonly target: string; readonly payload: unknown }): Promise<string> {
121
+ async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
121
122
  const content = formatSatoriOutbound(payload);
122
123
  const result = await this.#api('message', 'create', {
123
- channel_id: target,
124
+ channel_id: conversation.id,
124
125
  content,
125
126
  });
126
127
  const msgId = extractCreatedMessageId(result);
127
128
  logger.debug(formatCompact({
128
129
  op: 'satori_send',
129
130
  endpoint: this.#options.config.name,
130
- target,
131
+ channel: conversation.id,
131
132
  messageId: msgId || undefined,
132
133
  }));
133
- return msgId ? formatMessageId(target, msgId) : '';
134
+ return msgId ? formatMessageId(conversation.id, msgId) : '';
134
135
  }
135
136
 
136
137
  /** Test / internal: admit a gateway event when the endpoint is open. */
@@ -138,18 +139,16 @@ export class SatoriWsEndpoint implements EndpointInstance {
138
139
  if (!this.#open) return;
139
140
  if (body.login && !this.#login) this.#login = body.login;
140
141
  if (!isMessageEvent(body)) return;
141
- const target = resolveInboundTarget(body);
142
+ const conversation = satoriInboundConversation(String(this.#options.id), body);
142
143
  const content = formatInboundContent(body);
143
144
  const sender = resolveInboundSender(body);
144
- const messageId = formatMessageId(target, body.message.id);
145
145
  const selfId = this.#login?.user?.id ?? body.login?.user?.id;
146
146
  const mentioned = isSelfMentioned(body, selfId);
147
147
  void this.#options.gateway.receive({
148
- adapter: this.#options.id,
149
- target,
148
+ conversation,
149
+ message: { conversation, id: body.message.id },
150
150
  content,
151
151
  sender,
152
- id: messageId,
153
152
  metadata: Object.freeze({
154
153
  type: body.type,
155
154
  channelType: isPrivateChannelType(body) ? 'private' : 'group',
@@ -161,7 +160,7 @@ export class SatoriWsEndpoint implements EndpointInstance {
161
160
  }).catch((err) => {
162
161
  logger.warn(formatCompact({
163
162
  op: 'satori_gateway_receive_failed',
164
- target,
163
+ channel: conversation.id,
165
164
  error: err instanceof Error ? err.message : String(err),
166
165
  }));
167
166
  });
@@ -374,38 +373,36 @@ export class SatoriWebhookEndpoint implements EndpointInstance {
374
373
  }));
375
374
  }
376
375
 
377
- async send({ target, payload }: { readonly target: string; readonly payload: unknown }): Promise<string> {
376
+ async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
378
377
  const content = formatSatoriOutbound(payload);
379
378
  const result = await this.#api('message', 'create', {
380
- channel_id: target,
379
+ channel_id: conversation.id,
381
380
  content,
382
381
  });
383
382
  const msgId = extractCreatedMessageId(result);
384
383
  logger.debug(formatCompact({
385
384
  op: 'satori_send',
386
385
  endpoint: this.#options.config.name,
387
- target,
386
+ channel: conversation.id,
388
387
  messageId: msgId || undefined,
389
388
  }));
390
- return msgId ? formatMessageId(target, msgId) : '';
389
+ return msgId ? formatMessageId(conversation.id, msgId) : '';
391
390
  }
392
391
 
393
392
  admit(body: SatoriEventBody): void {
394
393
  if (!this.#open) return;
395
394
  if (body.login && !this.#login) this.#login = body.login;
396
395
  if (!isMessageEvent(body)) return;
397
- const target = resolveInboundTarget(body);
396
+ const conversation = satoriInboundConversation(String(this.#options.id), body);
398
397
  const content = formatInboundContent(body);
399
398
  const sender = resolveInboundSender(body);
400
- const messageId = formatMessageId(target, body.message.id);
401
399
  const selfId = this.#login?.user?.id ?? body.login?.user?.id;
402
400
  const mentioned = isSelfMentioned(body, selfId);
403
401
  void this.#options.gateway.receive({
404
- adapter: this.#options.id,
405
- target,
402
+ conversation,
403
+ message: { conversation, id: body.message.id },
406
404
  content,
407
405
  sender,
408
- id: messageId,
409
406
  metadata: Object.freeze({
410
407
  type: body.type,
411
408
  channelType: isPrivateChannelType(body) ? 'private' : 'group',
@@ -417,7 +414,7 @@ export class SatoriWebhookEndpoint implements EndpointInstance {
417
414
  }).catch((err) => {
418
415
  logger.warn(formatCompact({
419
416
  op: 'satori_gateway_receive_failed',
420
- target,
417
+ channel: conversation.id,
421
418
  error: err instanceof Error ? err.message : String(err),
422
419
  }));
423
420
  });
package/src/index.ts CHANGED
@@ -4,14 +4,12 @@ export {
4
4
  callSatoriApi,
5
5
  extractCreatedMessageId,
6
6
  formatInboundContent,
7
- formatMessageId,
8
7
  formatSatoriOutbound,
9
8
  isMessageEvent,
10
9
  isPrivateChannel,
11
- parseMessageRef,
12
10
  resolveInboundSender,
13
- resolveInboundTarget,
14
11
  resolveSatoriConfig,
12
+ satoriInboundConversation,
15
13
  type ResolvedSatoriWebhookConfig,
16
14
  type ResolvedSatoriWsConfig,
17
15
  type SatoriAdapterConfig,
package/src/protocol.ts CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { pickCredential } from '@zhin.js/adapter';
8
8
  import { isMediaRef } from '@zhin.js/core';
9
+ import type { ConversationKind, ConversationRef } from '@zhin.js/im-contract';
9
10
  import { formatCompact, getLogger } from '@zhin.js/logger';
10
11
 
11
12
  const logger = getLogger('satori');
@@ -227,9 +228,25 @@ export function formatInboundContent(body: SatoriEventBody & { message: SatoriMe
227
228
  return typeof content === 'string' ? content : String(content);
228
229
  }
229
230
 
230
- export function resolveInboundTarget(body: SatoriEventBody & { message: SatoriMessage }): string {
231
+
232
+ /**
233
+ * 入站归一化 → ConversationRef:Channel.type 1 (DIRECT) → kind 'private';
234
+ * 其余频道消息 → kind 'group',所属 guild 容器进 `parent`(kind 'channel')。
235
+ */
236
+ export function satoriInboundConversation(
237
+ endpointId: string,
238
+ body: SatoriEventBody & { message: SatoriMessage },
239
+ ): ConversationRef {
231
240
  const channel = body.channel ?? body.message.channel;
232
- return channel?.id ?? '';
241
+ const kind: ConversationKind = isPrivateChannel(channel) ? 'private' : 'group';
242
+ return {
243
+ endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
244
+ kind,
245
+ id: channel?.id ?? '',
246
+ ...(kind === 'group' && body.guild?.id
247
+ ? { parent: { kind: 'channel' as const, id: body.guild.id } }
248
+ : {}),
249
+ };
233
250
  }
234
251
 
235
252
  export function resolveInboundSender(body: SatoriEventBody & { message: SatoriMessage }): string {