@zhin.js/adapter-line 4.0.1 → 4.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,29 @@
1
1
  # @zhin.js/adapter-line
2
2
 
3
+ ## 4.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
+
19
+ ## 4.0.2
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies [696ab1b]
24
+ - @zhin.js/agent@1.1.2
25
+ - zhin.js@6.0.1
26
+
3
27
  ## 4.0.1
4
28
 
5
29
  ### Patch Changes
package/README.md CHANGED
@@ -24,8 +24,8 @@ pnpm add @zhin.js/adapter-line
24
24
  - `@zhin.js/plugin-runtime` — `plugin.ts`(`definePlugin`)
25
25
  - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
26
26
 
27
- 入站:`gateway.receive({ adapter, target: channelId, content: text, sender, metadata })`
28
- 出站:`send({ target, payload })` → Reply API(缓存 replyToken)或 Push API
27
+ 入站:`gateway.receive({ conversation, message: { conversation, id }, content: text, sender, metadata })`
28
+ 出站:`send({ conversation, payload })` → Reply API(缓存 replyToken)或 Push API
29
29
 
30
30
  ## 前置条件
31
31
 
package/lib/endpoint.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * LineEndpoint — lifecycle, outbound, admit, OpenAPI helpers for agent tools.
3
3
  */
4
- import type { EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
4
+ import type { EndpointInstance, EndpointManagement, 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';
@@ -39,10 +39,7 @@ export declare class LineEndpoint implements EndpointInstance {
39
39
  open(): void;
40
40
  close(): void;
41
41
  stop(): Promise<void>;
42
- send({ target, payload }: {
43
- readonly target: string;
44
- readonly payload: unknown;
45
- }): Promise<string>;
42
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
46
43
  /** Test / internal: admit a parsed event when open (non-webhook path). */
47
44
  admit(event: LineEvent): void;
48
45
  /**
package/lib/endpoint.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { formatCompact, getLogger } from '@zhin.js/logger';
2
2
  import { registerLineAgentEndpoint } from './line-agent-deps.js';
3
- import { formatInboundContent, formatOutboundMessages, generateMessageId, isMessageEvent, isValidLineRecipientId, resolveChannel, } from './protocol.js';
3
+ import { formatInboundContent, formatOutboundMessages, generateMessageId, isMessageEvent, isValidLineRecipientId, lineInboundConversation, } from './protocol.js';
4
4
  import { registerLineWebhookRoutes } from './webhook.js';
5
5
  const logger = getLogger('line');
6
6
  /** LINE replyToken 有效期短,过期后 reply 必 400;缓存带时间戳,超时弃用改走 push。 */
@@ -68,11 +68,13 @@ export class LineEndpoint {
68
68
  this.#started = false;
69
69
  logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
70
70
  }
71
- async send({ target, payload }) {
71
+ async send({ conversation, payload }) {
72
72
  const messages = formatOutboundMessages(payload);
73
73
  if (messages.length === 0) {
74
74
  throw new Error('No valid LINE messages to send');
75
75
  }
76
+ // LINE recipient id 前缀(U/G/R)自带场景信息,原生 id 即投递地址。
77
+ const target = conversation.id;
76
78
  const cached = this.#replyTokenCache.get(target);
77
79
  if (cached) {
78
80
  this.#replyTokenCache.delete(target);
@@ -101,16 +103,15 @@ export class LineEndpoint {
101
103
  admit(event) {
102
104
  if (!this.#open)
103
105
  return;
104
- const { channelId } = resolveChannel(event.source);
106
+ const conversation = lineInboundConversation(String(this.#options.id), event.source);
105
107
  if ('replyToken' in event && typeof event.replyToken === 'string') {
106
- this.#replyTokenCache.set(channelId, { token: event.replyToken, timestamp: Date.now() });
108
+ this.#replyTokenCache.set(conversation.id, { token: event.replyToken, timestamp: Date.now() });
107
109
  }
108
110
  void this.#options.gateway.receive({
109
- adapter: this.#options.id,
110
- target: channelId,
111
+ conversation,
112
+ message: { conversation, id: generateMessageId(event) },
111
113
  content: formatInboundContent(event),
112
- sender: event.source.userId || channelId,
113
- id: generateMessageId(event),
114
+ sender: event.source.userId || conversation.id,
114
115
  metadata: Object.freeze({
115
116
  eventType: event.type,
116
117
  sourceType: event.source.type,
@@ -121,7 +122,7 @@ export class LineEndpoint {
121
122
  }).catch((err) => {
122
123
  logger.warn(formatCompact({
123
124
  op: 'line_gateway_receive_failed',
124
- target: channelId,
125
+ target: `${conversation.kind}:${conversation.id}`,
125
126
  error: err instanceof Error ? err.message : String(err),
126
127
  }));
127
128
  });
package/lib/protocol.d.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * Canonicalization is owned by gateway/core before endpoint.send.
4
4
  */
5
5
  import type { IncomingMessage } from 'node:http';
6
+ import type { ConversationRef } from '@zhin.js/im-contract';
6
7
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
7
8
  export interface LineAdapterConfig {
8
9
  readonly name?: string;
@@ -136,6 +137,11 @@ export declare function normalizeWebhookPath(path: string): string;
136
137
  export declare function isMessageEvent(event: LineEvent): event is LineMessageEvent;
137
138
  export declare function isPostbackEvent(event: LineEvent): event is LinePostbackEvent;
138
139
  export declare function resolveChannel(source: LineSource): LineChannel;
140
+ /**
141
+ * 入站归一化 → ConversationRef:LINE user → private、group → group、room → channel。
142
+ * LINE 没有 guild/频道容器概念,无 parent;recipient id 前缀(U/G/R)自带场景信息。
143
+ */
144
+ export declare function lineInboundConversation(endpointId: string, source: LineSource): ConversationRef;
139
145
  export declare function generateMessageId(event: LineEvent): string;
140
146
  /** Build inbound text for MessageGateway.receive. */
141
147
  export declare function formatInboundContent(event: LineEvent): string;
package/lib/protocol.js CHANGED
@@ -54,6 +54,18 @@ export function resolveChannel(source) {
54
54
  return { channelType: 'private', channelId: '' };
55
55
  }
56
56
  }
57
+ /**
58
+ * 入站归一化 → ConversationRef:LINE user → private、group → group、room → channel。
59
+ * LINE 没有 guild/频道容器概念,无 parent;recipient id 前缀(U/G/R)自带场景信息。
60
+ */
61
+ export function lineInboundConversation(endpointId, source) {
62
+ const channel = resolveChannel(source);
63
+ return {
64
+ endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
65
+ kind: channel.channelType,
66
+ id: channel.channelId,
67
+ };
68
+ }
57
69
  export function generateMessageId(event) {
58
70
  if (isMessageEvent(event) && event.message?.id)
59
71
  return event.message.id;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-line",
3
- "version": "4.0.1",
3
+ "version": "4.0.3",
4
4
  "description": "Zhin.js LINE Messaging API adapter for Plugin Runtime (HTTP webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -32,21 +32,22 @@
32
32
  "directory": "plugins/adapters/line"
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",
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",
39
40
  "@zhin.js/logger": "1.0.75",
40
- "@zhin.js/plugin-runtime": "1.1.2"
41
+ "@zhin.js/plugin-runtime": "1.1.3"
41
42
  },
42
43
  "peerDependencies": {
43
44
  "zod": "^4.0.0",
44
- "@zhin.js/adapter": "1.1.4",
45
- "@zhin.js/agent": "1.1.1",
46
- "@zhin.js/core": "1.5.1",
47
- "@zhin.js/host-http": "1.0.5",
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/host-http": "1.0.6",
49
+ "@zhin.js/plugin-runtime": "1.1.3",
50
+ "zhin.js": "6.0.2"
50
51
  },
51
52
  "peerDependenciesMeta": {
52
53
  "zhin.js": {
@@ -64,7 +65,7 @@
64
65
  "typescript": "^6.0.3",
65
66
  "vitest": "^4.1.10",
66
67
  "zod": "^4.4.3",
67
- "@zhin.js/agent": "1.1.1"
68
+ "@zhin.js/agent": "1.1.3"
68
69
  },
69
70
  "files": [
70
71
  "adapters",
package/src/endpoint.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * LineEndpoint — lifecycle, outbound, admit, OpenAPI helpers for agent tools.
3
3
  */
4
- import type { EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
4
+ import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
5
5
  import type { MessageGateway } from '@zhin.js/core/runtime';
6
6
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
7
7
  import { formatCompact, getLogger } from '@zhin.js/logger';
@@ -13,7 +13,7 @@ import {
13
13
  generateMessageId,
14
14
  isMessageEvent,
15
15
  isValidLineRecipientId,
16
- resolveChannel,
16
+ lineInboundConversation,
17
17
  type LineApiResponse,
18
18
  type LineEvent,
19
19
  type ResolvedLineConfig,
@@ -117,11 +117,13 @@ export class LineEndpoint implements EndpointInstance {
117
117
  logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
118
118
  }
119
119
 
120
- async send({ target, payload }: { readonly target: string; readonly payload: unknown }): Promise<string> {
120
+ async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
121
121
  const messages = formatOutboundMessages(payload);
122
122
  if (messages.length === 0) {
123
123
  throw new Error('No valid LINE messages to send');
124
124
  }
125
+ // LINE recipient id 前缀(U/G/R)自带场景信息,原生 id 即投递地址。
126
+ const target = conversation.id;
125
127
 
126
128
  const cached = this.#replyTokenCache.get(target);
127
129
  if (cached) {
@@ -152,16 +154,15 @@ export class LineEndpoint implements EndpointInstance {
152
154
  /** Test / internal: admit a parsed event when open (non-webhook path). */
153
155
  admit(event: LineEvent): void {
154
156
  if (!this.#open) return;
155
- const { channelId } = resolveChannel(event.source);
157
+ const conversation = lineInboundConversation(String(this.#options.id), event.source);
156
158
  if ('replyToken' in event && typeof event.replyToken === 'string') {
157
- this.#replyTokenCache.set(channelId, { token: event.replyToken, timestamp: Date.now() });
159
+ this.#replyTokenCache.set(conversation.id, { token: event.replyToken, timestamp: Date.now() });
158
160
  }
159
161
  void this.#options.gateway.receive({
160
- adapter: this.#options.id,
161
- target: channelId,
162
+ conversation,
163
+ message: { conversation, id: generateMessageId(event) },
162
164
  content: formatInboundContent(event),
163
- sender: event.source.userId || channelId,
164
- id: generateMessageId(event),
165
+ sender: event.source.userId || conversation.id,
165
166
  metadata: Object.freeze({
166
167
  eventType: event.type,
167
168
  sourceType: event.source.type,
@@ -172,7 +173,7 @@ export class LineEndpoint implements EndpointInstance {
172
173
  }).catch((err) => {
173
174
  logger.warn(formatCompact({
174
175
  op: 'line_gateway_receive_failed',
175
- target: channelId,
176
+ target: `${conversation.kind}:${conversation.id}`,
176
177
  error: err instanceof Error ? err.message : String(err),
177
178
  }));
178
179
  });
package/src/protocol.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  import { createHmac, timingSafeEqual } from 'node:crypto';
7
7
  import type { IncomingMessage } from 'node:http';
8
8
  import { isMediaRef } from '@zhin.js/core';
9
+ import type { ConversationRef } from '@zhin.js/im-contract';
9
10
  import { formatCompact, getLogger } from '@zhin.js/logger';
10
11
 
11
12
  const logger = getLogger('line');
@@ -217,6 +218,19 @@ export function resolveChannel(source: LineSource): LineChannel {
217
218
  }
218
219
  }
219
220
 
221
+ /**
222
+ * 入站归一化 → ConversationRef:LINE user → private、group → group、room → channel。
223
+ * LINE 没有 guild/频道容器概念,无 parent;recipient id 前缀(U/G/R)自带场景信息。
224
+ */
225
+ export function lineInboundConversation(endpointId: string, source: LineSource): ConversationRef {
226
+ const channel = resolveChannel(source);
227
+ return {
228
+ endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
229
+ kind: channel.channelType,
230
+ id: channel.channelId,
231
+ };
232
+ }
233
+
220
234
  export function generateMessageId(event: LineEvent): string {
221
235
  if (isMessageEvent(event) && event.message?.id) return event.message.id;
222
236
  return `${event.type}-${event.timestamp}`;