@zhin.js/adapter-telegram 7.0.2 → 7.0.3

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,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 7.0.3
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/agent@1.1.3
17
+ - zhin.js@6.0.2
18
+
3
19
  ## 7.0.2
4
20
 
5
21
  ### Patch Changes
package/README.md CHANGED
@@ -7,7 +7,7 @@ Zhin.js Telegram Bot API 适配器(Plugin Runtime),默认通过 **长轮
7
7
  - 长轮询 `getUpdates` 入站(默认;无需公网 IP / host-http)
8
8
  - 解析 text / image / video / audio / voice / document / sticker / location / callback_query
9
9
  - 支持私聊与群组
10
- - 出站 `send({ target, payload })` → Bot API(text / media / keyboard)
10
+ - 出站 `send({ conversation, payload })` → Bot API(text / media / keyboard)
11
11
  - 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
12
12
  - Webhook 模式延期(需 `httpHostToken`);配置 `polling: false` 会明确报错
13
13
 
@@ -25,8 +25,8 @@ pnpm add @zhin.js/adapter-telegram
25
25
  - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
26
26
  - **无需** `@zhin.js/host-http`(polling 路径)
27
27
 
28
- 入站:`gateway.receive({ adapter, target: chatId, content: text, sender, metadata })`
29
- 出站:`send({ target, payload })` → Telegram Bot API
28
+ 入站:`gateway.receive({ conversation, message, content, sender, metadata })`(`conversation` 为 ConversationRef,由 `telegramInboundConversation` 归一化)
29
+ 出站:`send({ conversation, payload })` → Telegram Bot API
30
30
 
31
31
  ### 平台权限(platform permit)
32
32
 
package/lib/endpoint.d.ts CHANGED
@@ -40,7 +40,7 @@ export declare class TelegramEndpoint implements EndpointInstance {
40
40
  open(): void;
41
41
  close(): void;
42
42
  stop(): Promise<void>;
43
- send({ target, conversation, payload }: EndpointSendRequest): Promise<string>;
43
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
44
44
  recallMessage(messageId: string): Promise<void>;
45
45
  /** Test / internal: admit a message when open. */
46
46
  admit(msg: TelegramMessage): void;
package/lib/endpoint.js CHANGED
@@ -2,11 +2,11 @@
2
2
  * TelegramEndpoint — lifecycle, outbound, admit, Bot API helpers for agent tools.
3
3
  */
4
4
  import { readFile } from 'node:fs/promises';
5
- import { formatLegacyMessageReference, nativeConversationId, parseLegacyMessageReference, } from '@zhin.js/im-contract';
5
+ import { formatLegacyMessageRef, nativeConversationId, parseLegacyMessageReference, } from '@zhin.js/im-contract';
6
6
  import { formatCompact, getLogger } from '@zhin.js/logger';
7
7
  import { runTelegramPollLoop } from './polling.js';
8
8
  import { normalizeTelegramChatMember } from './platform-permit.js';
9
- import { botApiUrl, buildWebhookUrl, formatCallbackContent, formatCallbackSegments, formatInboundContent, formatInboundSegments, formatOutboundPlan, resolveChannel, senderDisplayName, } from './protocol.js';
9
+ import { botApiUrl, buildWebhookUrl, formatCallbackContent, formatCallbackSegments, formatInboundContent, formatInboundSegments, formatOutboundPlan, resolveTelegramChannelType, senderDisplayName, telegramInboundConversation, } from './protocol.js';
10
10
  import { registerTelegramAgentEndpoint } from './telegram-agent-deps.js';
11
11
  import { registerTelegramWebhookRoutes } from './webhook.js';
12
12
  const logger = getLogger('telegram');
@@ -131,9 +131,8 @@ export class TelegramEndpoint {
131
131
  this.#started = false;
132
132
  logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
133
133
  }
134
- async send({ target, conversation, payload }) {
135
- const chatId = nativeConversationId(target, conversation);
136
- const plan = formatOutboundPlan(chatId, payload);
134
+ async send({ conversation, payload }) {
135
+ const plan = formatOutboundPlan(conversation.id, payload);
137
136
  let lastId = '';
138
137
  for (const action of plan.actions) {
139
138
  const form = await this.#buildUploadForm(action.params, plan.uploads);
@@ -145,7 +144,7 @@ export class TelegramEndpoint {
145
144
  }
146
145
  if (!lastId)
147
146
  return `telegram-${Date.now()}`;
148
- return formatLegacyMessageReference({ target, messageId: lastId });
147
+ return formatLegacyMessageRef({ conversation, id: lastId });
149
148
  }
150
149
  async recallMessage(messageId) {
151
150
  if (!messageId || messageId.startsWith('telegram-'))
@@ -188,26 +187,25 @@ export class TelegramEndpoint {
188
187
  admit(msg) {
189
188
  if (!this.#open)
190
189
  return;
191
- const { channelId } = resolveChannel(msg);
192
- void this.#admitWithSenderRole(msg, channelId).catch((err) => {
190
+ const conversation = telegramInboundConversation(String(this.#options.id), msg.chat);
191
+ void this.#admitWithSenderRole(msg, conversation).catch((err) => {
193
192
  logger.warn(formatCompact({
194
193
  op: 'telegram_gateway_receive_failed',
195
- target: channelId,
194
+ target: `${conversation.kind}:${conversation.id}`,
196
195
  error: err instanceof Error ? err.message : String(err),
197
196
  }));
198
197
  });
199
198
  }
200
- async #admitWithSenderRole(msg, channelId) {
199
+ async #admitWithSenderRole(msg, conversation) {
201
200
  const permit = await this.#resolveGroupSenderPermit(msg);
202
201
  // 新 Runtime Message.content 为纯文本:@ 本机只能经 metadata 传递
203
202
  const mentioned = this.#isBotMentioned(msg);
204
203
  await this.#options.gateway.receive({
205
- adapter: this.#options.id,
206
- target: channelId,
204
+ conversation,
205
+ message: { conversation, id: String(msg.message_id) },
207
206
  content: formatInboundContent(msg),
208
207
  segments: formatInboundSegments(msg),
209
208
  sender: senderDisplayName(msg.from),
210
- id: String(msg.message_id),
211
209
  metadata: Object.freeze({
212
210
  endpoint: this.#options.config.name,
213
211
  // Preserve Telegram's native value below, but publish the canonical
@@ -287,15 +285,18 @@ export class TelegramEndpoint {
287
285
  admitCallback(query) {
288
286
  if (!this.#open)
289
287
  return;
288
+ const endpointId = String(this.#options.id);
290
289
  const msg = query.message;
291
- const channelId = msg ? resolveChannel(msg).channelId : String(query.from.id);
290
+ // 无挂载消息的 callback(如 inline 模式)退化为 sender 私聊会话
291
+ const conversation = msg
292
+ ? telegramInboundConversation(endpointId, msg.chat)
293
+ : telegramInboundConversation(endpointId, { id: query.from.id, type: 'private' });
292
294
  void this.#options.gateway.receive({
293
- adapter: this.#options.id,
294
- target: channelId,
295
+ conversation,
296
+ message: { conversation, id: query.id },
295
297
  content: formatCallbackContent(query),
296
298
  segments: formatCallbackSegments(query),
297
299
  sender: senderDisplayName(query.from),
298
- id: query.id,
299
300
  metadata: Object.freeze({
300
301
  endpoint: this.#options.config.name,
301
302
  eventType: 'callback_query',
@@ -305,7 +306,7 @@ export class TelegramEndpoint {
305
306
  }).catch((err) => {
306
307
  logger.warn(formatCompact({
307
308
  op: 'telegram_gateway_receive_failed',
308
- target: channelId,
309
+ target: `${conversation.kind}:${conversation.id}`,
309
310
  error: err instanceof Error ? err.message : String(err),
310
311
  }));
311
312
  });
@@ -413,10 +414,3 @@ export class TelegramEndpoint {
413
414
  });
414
415
  }
415
416
  }
416
- function resolveTelegramChannelType(chatType) {
417
- if (chatType === 'private')
418
- return 'private';
419
- if (chatType === 'channel')
420
- return 'channel';
421
- return 'group';
422
- }
package/lib/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { TelegramEndpoint, type TelegramEndpointOptions, type TelegramFetch, } from './endpoint.js';
2
- export { botApiUrl, formatCallbackContent, formatInboundContent, formatOutboundActions, formatOutboundPlan, normalizeWebhookPath, resolveChannel, resolveTelegramConfig, senderDisplayName, type ResolvedTelegramConfig, type TelegramAdapterConfig, type TelegramCallbackQuery, type TelegramChat, type TelegramChatMember, type TelegramMessage, type TelegramOutboundAction, type TelegramOutboundPlan, type TelegramOutboundUpload, type TelegramUpdate, type TelegramUser, type TelegramWireSegment, } from './protocol.js';
2
+ export { botApiUrl, formatCallbackContent, formatInboundContent, formatOutboundActions, formatOutboundPlan, normalizeWebhookPath, resolveTelegramConfig, senderDisplayName, type ResolvedTelegramConfig, type TelegramAdapterConfig, type TelegramCallbackQuery, type TelegramChat, type TelegramChatMember, type TelegramMessage, type TelegramOutboundAction, type TelegramOutboundPlan, type TelegramOutboundUpload, type TelegramUpdate, type TelegramUser, type TelegramWireSegment, } from './protocol.js';
3
3
  export { getTelegramAgentDeps, registerTelegramAgentEndpoint, setTelegramAgentDeps, type TelegramAgentDeps, type TelegramAgentEndpoint, } from './telegram-agent-deps.js';
4
4
  export { checkTelegramPlatformPermit, normalizeTelegramChatMember, platformPermit, registerTelegramPlatformPermitChecker, telegramGroupPermitResolver, } from './platform-permit.js';
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { TelegramEndpoint, } from './endpoint.js';
2
- export { botApiUrl, formatCallbackContent, formatInboundContent, formatOutboundActions, formatOutboundPlan, normalizeWebhookPath, resolveChannel, resolveTelegramConfig, senderDisplayName, } from './protocol.js';
2
+ export { botApiUrl, formatCallbackContent, formatInboundContent, formatOutboundActions, formatOutboundPlan, normalizeWebhookPath, resolveTelegramConfig, senderDisplayName, } from './protocol.js';
3
3
  export { getTelegramAgentDeps, registerTelegramAgentEndpoint, setTelegramAgentDeps, } from './telegram-agent-deps.js';
4
4
  export { checkTelegramPlatformPermit, normalizeTelegramChatMember, platformPermit, registerTelegramPlatformPermitChecker, telegramGroupPermitResolver, } from './platform-permit.js';
package/lib/protocol.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import type { IncomingMessage } from 'node:http';
6
6
  import type { Segment } from '@zhin.js/core/runtime';
7
+ import type { ConversationKind, ConversationRef } from '@zhin.js/im-contract';
7
8
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
8
9
  export interface TelegramAdapterConfig {
9
10
  readonly name?: string;
@@ -236,10 +237,13 @@ export declare function readTextBody(request: IncomingMessage, options?: {
236
237
  readonly limit?: number;
237
238
  }): Promise<string>;
238
239
  export declare function botApiUrl(config: Pick<ResolvedTelegramConfig, 'apiBaseUrl' | 'token'>, method: string): string;
239
- export declare function resolveChannel(msg: Pick<TelegramMessage, 'chat'>): {
240
- readonly channelType: 'private' | 'group';
241
- readonly channelId: string;
242
- };
240
+ /** Telegram chat.type canonical 会话 kind(supergroup 即 group,无容器层级故无 parent)。 */
241
+ export declare function resolveTelegramChannelType(chatType: TelegramChat['type']): ConversationKind;
242
+ /**
243
+ * 入站归一化 → ConversationRef:Telegram 无 guild/群组容器层级,
244
+ * kind 直取 chat.type(private/group/supergroup/channel),无 parent。
245
+ */
246
+ export declare function telegramInboundConversation(endpointId: string, chat: Pick<TelegramChat, 'id' | 'type'>): ConversationRef;
243
247
  export declare function senderDisplayName(user?: TelegramUser): string;
244
248
  /** Build inbound text for MessageGateway.receive. */
245
249
  export declare function formatInboundContent(msg: TelegramMessage): string;
package/lib/protocol.js CHANGED
@@ -76,10 +76,23 @@ export async function readTextBody(request, options = {}) {
76
76
  export function botApiUrl(config, method) {
77
77
  return `${config.apiBaseUrl}/bot${config.token}/${method}`;
78
78
  }
79
- export function resolveChannel(msg) {
79
+ /** Telegram chat.type → canonical 会话 kind(supergroup 即 group,无容器层级故无 parent)。 */
80
+ export function resolveTelegramChannelType(chatType) {
81
+ if (chatType === 'private')
82
+ return 'private';
83
+ if (chatType === 'channel')
84
+ return 'channel';
85
+ return 'group';
86
+ }
87
+ /**
88
+ * 入站归一化 → ConversationRef:Telegram 无 guild/群组容器层级,
89
+ * kind 直取 chat.type(private/group/supergroup/channel),无 parent。
90
+ */
91
+ export function telegramInboundConversation(endpointId, chat) {
80
92
  return {
81
- channelType: msg.chat.type === 'private' ? 'private' : 'group',
82
- channelId: String(msg.chat.id),
93
+ endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
94
+ kind: resolveTelegramChannelType(chat.type),
95
+ id: String(chat.id),
83
96
  };
84
97
  }
85
98
  export function senderDisplayName(user) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-telegram",
3
- "version": "7.0.2",
3
+ "version": "7.0.3",
4
4
  "description": "Zhin.js Telegram Bot API adapter for Plugin Runtime (long-poll getUpdates)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -32,21 +32,21 @@
32
32
  "directory": "plugins/adapters/telegram"
33
33
  },
34
34
  "dependencies": {
35
- "@zhin.js/adapter": "1.1.4",
36
- "@zhin.js/command": "1.0.6",
37
- "@zhin.js/core": "1.5.1",
38
- "@zhin.js/host-http": "1.0.5",
39
- "@zhin.js/im-contract": "1.0.0",
35
+ "@zhin.js/adapter": "1.1.5",
36
+ "@zhin.js/command": "1.0.7",
37
+ "@zhin.js/core": "1.5.2",
38
+ "@zhin.js/host-http": "1.0.6",
39
+ "@zhin.js/im-contract": "1.0.1",
40
40
  "@zhin.js/logger": "1.0.75",
41
- "@zhin.js/plugin-runtime": "1.1.2"
41
+ "@zhin.js/plugin-runtime": "1.1.3"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "zod": "^4.0.0",
45
- "@zhin.js/adapter": "1.1.4",
46
- "@zhin.js/agent": "1.1.2",
47
- "@zhin.js/core": "1.5.1",
48
- "@zhin.js/plugin-runtime": "1.1.2",
49
- "zhin.js": "6.0.1"
45
+ "@zhin.js/adapter": "1.1.5",
46
+ "@zhin.js/agent": "1.1.3",
47
+ "@zhin.js/core": "1.5.2",
48
+ "@zhin.js/plugin-runtime": "1.1.3",
49
+ "zhin.js": "6.0.2"
50
50
  },
51
51
  "peerDependenciesMeta": {
52
52
  "zhin.js": {
@@ -64,9 +64,9 @@
64
64
  "typescript": "^6.0.3",
65
65
  "vitest": "^4.1.10",
66
66
  "zod": "^4.4.3",
67
- "@zhin.js/agent": "1.1.2",
68
- "@zhin.js/host-http": "1.0.5",
69
- "zhin.js": "6.0.1"
67
+ "@zhin.js/agent": "1.1.3",
68
+ "@zhin.js/host-http": "1.0.6",
69
+ "zhin.js": "6.0.2"
70
70
  },
71
71
  "files": [
72
72
  "adapters",
package/src/endpoint.ts CHANGED
@@ -6,9 +6,10 @@ import type { EndpointControl, EndpointInstance, EndpointSendRequest } from '@zh
6
6
  import type { MessageGateway } from '@zhin.js/core/runtime';
7
7
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
8
8
  import {
9
- formatLegacyMessageReference,
9
+ formatLegacyMessageRef,
10
10
  nativeConversationId,
11
11
  parseLegacyMessageReference,
12
+ type ConversationRef,
12
13
  } from '@zhin.js/im-contract';
13
14
  import { formatCompact, getLogger } from '@zhin.js/logger';
14
15
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
@@ -22,8 +23,9 @@ import {
22
23
  formatInboundContent,
23
24
  formatInboundSegments,
24
25
  formatOutboundPlan,
25
- resolveChannel,
26
+ resolveTelegramChannelType,
26
27
  senderDisplayName,
28
+ telegramInboundConversation,
27
29
  type ResolvedTelegramConfig,
28
30
  type TelegramCallbackQuery,
29
31
  type TelegramChatMember,
@@ -207,9 +209,8 @@ export class TelegramEndpoint implements EndpointInstance {
207
209
  logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
208
210
  }
209
211
 
210
- async send({ target, conversation, payload }: EndpointSendRequest): Promise<string> {
211
- const chatId = nativeConversationId(target, conversation);
212
- const plan = formatOutboundPlan(chatId, payload);
212
+ async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
213
+ const plan = formatOutboundPlan(conversation.id, payload);
213
214
  let lastId = '';
214
215
  for (const action of plan.actions) {
215
216
  const form = await this.#buildUploadForm(action.params, plan.uploads);
@@ -219,7 +220,7 @@ export class TelegramEndpoint implements EndpointInstance {
219
220
  if (result.message_id != null) lastId = String(result.message_id);
220
221
  }
221
222
  if (!lastId) return `telegram-${Date.now()}`;
222
- return formatLegacyMessageReference({ target, messageId: lastId });
223
+ return formatLegacyMessageRef({ conversation, id: lastId });
223
224
  }
224
225
 
225
226
  async recallMessage(messageId: string): Promise<void> {
@@ -266,27 +267,26 @@ export class TelegramEndpoint implements EndpointInstance {
266
267
  /** Test / internal: admit a message when open. */
267
268
  admit(msg: TelegramMessage): void {
268
269
  if (!this.#open) return;
269
- const { channelId } = resolveChannel(msg);
270
- void this.#admitWithSenderRole(msg, channelId).catch((err) => {
270
+ const conversation = telegramInboundConversation(String(this.#options.id), msg.chat);
271
+ void this.#admitWithSenderRole(msg, conversation).catch((err) => {
271
272
  logger.warn(formatCompact({
272
273
  op: 'telegram_gateway_receive_failed',
273
- target: channelId,
274
+ target: `${conversation.kind}:${conversation.id}`,
274
275
  error: err instanceof Error ? err.message : String(err),
275
276
  }));
276
277
  });
277
278
  }
278
279
 
279
- async #admitWithSenderRole(msg: TelegramMessage, channelId: string): Promise<void> {
280
+ async #admitWithSenderRole(msg: TelegramMessage, conversation: ConversationRef): Promise<void> {
280
281
  const permit = await this.#resolveGroupSenderPermit(msg);
281
282
  // 新 Runtime Message.content 为纯文本:@ 本机只能经 metadata 传递
282
283
  const mentioned = this.#isBotMentioned(msg);
283
284
  await this.#options.gateway.receive({
284
- adapter: this.#options.id,
285
- target: channelId,
285
+ conversation,
286
+ message: { conversation, id: String(msg.message_id) },
286
287
  content: formatInboundContent(msg),
287
288
  segments: formatInboundSegments(msg),
288
289
  sender: senderDisplayName(msg.from),
289
- id: String(msg.message_id),
290
290
  metadata: Object.freeze({
291
291
  endpoint: this.#options.config.name,
292
292
  // Preserve Telegram's native value below, but publish the canonical
@@ -360,15 +360,18 @@ export class TelegramEndpoint implements EndpointInstance {
360
360
  /** Test / internal: admit a callback query when open. */
361
361
  admitCallback(query: TelegramCallbackQuery): void {
362
362
  if (!this.#open) return;
363
+ const endpointId = String(this.#options.id);
363
364
  const msg = query.message;
364
- const channelId = msg ? resolveChannel(msg).channelId : String(query.from.id);
365
+ // 无挂载消息的 callback(如 inline 模式)退化为 sender 私聊会话
366
+ const conversation = msg
367
+ ? telegramInboundConversation(endpointId, msg.chat)
368
+ : telegramInboundConversation(endpointId, { id: query.from.id, type: 'private' });
365
369
  void this.#options.gateway.receive({
366
- adapter: this.#options.id,
367
- target: channelId,
370
+ conversation,
371
+ message: { conversation, id: query.id },
368
372
  content: formatCallbackContent(query),
369
373
  segments: formatCallbackSegments(query),
370
374
  sender: senderDisplayName(query.from),
371
- id: query.id,
372
375
  metadata: Object.freeze({
373
376
  endpoint: this.#options.config.name,
374
377
  eventType: 'callback_query',
@@ -378,7 +381,7 @@ export class TelegramEndpoint implements EndpointInstance {
378
381
  }).catch((err) => {
379
382
  logger.warn(formatCompact({
380
383
  op: 'telegram_gateway_receive_failed',
381
- target: channelId,
384
+ target: `${conversation.kind}:${conversation.id}`,
382
385
  error: err instanceof Error ? err.message : String(err),
383
386
  }));
384
387
  });
@@ -521,11 +524,3 @@ export class TelegramEndpoint implements EndpointInstance {
521
524
  });
522
525
  }
523
526
  }
524
-
525
- function resolveTelegramChannelType(
526
- chatType: TelegramMessage['chat']['type'],
527
- ): 'private' | 'group' | 'channel' {
528
- if (chatType === 'private') return 'private';
529
- if (chatType === 'channel') return 'channel';
530
- return 'group';
531
- }
package/src/index.ts CHANGED
@@ -11,7 +11,6 @@ export {
11
11
  formatOutboundActions,
12
12
  formatOutboundPlan,
13
13
  normalizeWebhookPath,
14
- resolveChannel,
15
14
  resolveTelegramConfig,
16
15
  senderDisplayName,
17
16
  type ResolvedTelegramConfig,
package/src/protocol.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  import type { IncomingMessage } from 'node:http';
7
7
  import { isMediaRef } from '@zhin.js/core';
8
8
  import type { Segment } from '@zhin.js/core/runtime';
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('telegram');
@@ -321,13 +322,27 @@ export function botApiUrl(config: Pick<ResolvedTelegramConfig, 'apiBaseUrl' | 't
321
322
  return `${config.apiBaseUrl}/bot${config.token}/${method}`;
322
323
  }
323
324
 
324
- export function resolveChannel(msg: Pick<TelegramMessage, 'chat'>): {
325
- readonly channelType: 'private' | 'group';
326
- readonly channelId: string;
327
- } {
325
+ /** Telegram chat.type canonical 会话 kind(supergroup 即 group,无容器层级故无 parent)。 */
326
+ export function resolveTelegramChannelType(
327
+ chatType: TelegramChat['type'],
328
+ ): ConversationKind {
329
+ if (chatType === 'private') return 'private';
330
+ if (chatType === 'channel') return 'channel';
331
+ return 'group';
332
+ }
333
+
334
+ /**
335
+ * 入站归一化 → ConversationRef:Telegram 无 guild/群组容器层级,
336
+ * kind 直取 chat.type(private/group/supergroup/channel),无 parent。
337
+ */
338
+ export function telegramInboundConversation(
339
+ endpointId: string,
340
+ chat: Pick<TelegramChat, 'id' | 'type'>,
341
+ ): ConversationRef {
328
342
  return {
329
- channelType: msg.chat.type === 'private' ? 'private' : 'group',
330
- channelId: String(msg.chat.id),
343
+ endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
344
+ kind: resolveTelegramChannelType(chat.type),
345
+ id: String(chat.id),
331
346
  };
332
347
  }
333
348