@zhin.js/adapter-line 4.0.2 → 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 +16 -0
- package/README.md +2 -2
- package/lib/endpoint.d.ts +2 -5
- package/lib/endpoint.js +10 -9
- package/lib/protocol.d.ts +6 -0
- package/lib/protocol.js +12 -0
- package/package.json +14 -13
- package/src/endpoint.ts +11 -10
- package/src/protocol.ts +14 -0
- /package/commands/endpoint/add/{[name:string].js → [name].js} +0 -0
- /package/commands/endpoint/add/{[name:string].ts → [name].ts} +0 -0
- /package/commands/endpoint/remove/{[name:string].js → [name].js} +0 -0
- /package/commands/endpoint/remove/{[name:string].ts → [name].ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
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
|
+
|
|
3
19
|
## 4.0.2
|
|
4
20
|
|
|
5
21
|
### 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({
|
|
28
|
-
出站:`send({
|
|
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({
|
|
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,
|
|
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({
|
|
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
|
|
106
|
+
const conversation = lineInboundConversation(String(this.#options.id), event.source);
|
|
105
107
|
if ('replyToken' in event && typeof event.replyToken === 'string') {
|
|
106
|
-
this.#replyTokenCache.set(
|
|
108
|
+
this.#replyTokenCache.set(conversation.id, { token: event.replyToken, timestamp: Date.now() });
|
|
107
109
|
}
|
|
108
110
|
void this.#options.gateway.receive({
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
conversation,
|
|
112
|
+
message: { conversation, id: generateMessageId(event) },
|
|
111
113
|
content: formatInboundContent(event),
|
|
112
|
-
sender: event.source.userId ||
|
|
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:
|
|
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.
|
|
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.
|
|
36
|
-
"@zhin.js/command": "1.0.
|
|
37
|
-
"@zhin.js/core": "1.5.
|
|
38
|
-
"@zhin.js/host-http": "1.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",
|
|
39
40
|
"@zhin.js/logger": "1.0.75",
|
|
40
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
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.
|
|
45
|
-
"@zhin.js/agent": "1.1.
|
|
46
|
-
"@zhin.js/core": "1.5.
|
|
47
|
-
"@zhin.js/host-http": "1.0.
|
|
48
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
49
|
-
"zhin.js": "6.0.
|
|
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.
|
|
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
|
-
|
|
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({
|
|
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
|
|
157
|
+
const conversation = lineInboundConversation(String(this.#options.id), event.source);
|
|
156
158
|
if ('replyToken' in event && typeof event.replyToken === 'string') {
|
|
157
|
-
this.#replyTokenCache.set(
|
|
159
|
+
this.#replyTokenCache.set(conversation.id, { token: event.replyToken, timestamp: Date.now() });
|
|
158
160
|
}
|
|
159
161
|
void this.#options.gateway.receive({
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
conversation,
|
|
163
|
+
message: { conversation, id: generateMessageId(event) },
|
|
162
164
|
content: formatInboundContent(event),
|
|
163
|
-
sender: event.source.userId ||
|
|
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:
|
|
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}`;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|