@zhin.js/adapter-line 2.0.2 → 3.0.0

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +46 -24
  3. package/adapters/line.ts +26 -0
  4. package/agent/tools/get_group_members.ts +2 -2
  5. package/agent/tools/get_profile.ts +2 -2
  6. package/lib/endpoint.d.ts +46 -0
  7. package/lib/endpoint.js +144 -0
  8. package/lib/index.d.ts +4 -0
  9. package/lib/index.js +4 -0
  10. package/lib/line-agent-deps.d.ts +24 -0
  11. package/lib/line-agent-deps.js +33 -0
  12. package/lib/protocol.d.ts +151 -0
  13. package/lib/protocol.js +212 -0
  14. package/lib/webhook.d.ts +13 -0
  15. package/lib/webhook.js +50 -0
  16. package/package.json +42 -25
  17. package/plugin.ts +8 -0
  18. package/schema.json +50 -0
  19. package/src/endpoint.ts +148 -554
  20. package/src/index.ts +50 -41
  21. package/src/line-agent-deps.ts +32 -23
  22. package/src/protocol.ts +384 -0
  23. package/src/webhook.ts +79 -0
  24. package/lib/agent/tools/get_group_members.js +0 -24
  25. package/lib/agent/tools/get_group_members.js.map +0 -1
  26. package/lib/agent/tools/get_profile.js +0 -24
  27. package/lib/agent/tools/get_profile.js.map +0 -1
  28. package/lib/src/adapter.js +0 -22
  29. package/lib/src/adapter.js.map +0 -1
  30. package/lib/src/endpoint.js +0 -536
  31. package/lib/src/endpoint.js.map +0 -1
  32. package/lib/src/index.js +0 -30
  33. package/lib/src/index.js.map +0 -1
  34. package/lib/src/line-agent-deps.js +0 -26
  35. package/lib/src/line-agent-deps.js.map +0 -1
  36. package/lib/src/segment-mapper.js +0 -2
  37. package/lib/src/segment-mapper.js.map +0 -1
  38. package/lib/src/types.js +0 -5
  39. package/lib/src/types.js.map +0 -1
  40. package/plugin.yml +0 -3
  41. package/src/adapter.ts +0 -29
  42. package/src/segment-mapper.ts +0 -1
  43. package/src/types.ts +0 -130
package/CHANGELOG.md CHANGED
@@ -1,5 +1,59 @@
1
1
  # @zhin.js/adapter-line
2
2
 
3
+ ## 3.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 7db69c1: 命令前缀改为适配器配置项:`MessageDispatcher` 不再硬编码 `/`,默认按消息所属适配器实例 config 的 `commandPrefix` 解析(默认 `''` 无前缀,任意文本按命令匹配),`endpoints[i].commandPrefix` 逐项覆盖;`ImRuntime({ commandPrefix })` 仍可设全局静态前缀。全部 20 个平台适配器 schema 新增 `commandPrefix` 属性。
8
+
9
+ BREAKING(行为变化):未配置时命令不再需要 `/` 前缀——原 `/zt` 写法不再命中,直接发 `zt` 即可;需要斜杠风格的适配器请在配置里显式设 `commandPrefix: '/'`。
10
+
11
+ - 713445c: 适配器配置格式定稿(不兼容旧格式):`plugins.<adapter>` 顶层仅共享字段 + `commandPrefix`,`endpoints[i]` 携带 endpoint 级字段(`name` + 凭据,各 schema 已类型化),`endpoints` 为必填(icqq 另需顶层 `master`);icqq 新增 `trusted` 列表(顶层/逐项均可)。scaffold-wizard 全部字段式与自定义 configure() 产出改为新格式,examples(full-bot / qq-games-bot)与 20 个适配器 README 同步迁移。
12
+ - Updated dependencies [7db69c1]
13
+ - Updated dependencies [e5c84ed]
14
+ - Updated dependencies [3ea84a0]
15
+ - Updated dependencies [1ddcd70]
16
+ - Updated dependencies [ac9da66]
17
+ - @zhin.js/core@1.4.0
18
+ - @zhin.js/adapter@1.1.0
19
+ - @zhin.js/plugin-runtime@1.1.0
20
+ - @zhin.js/agent@1.0.5
21
+ - @zhin.js/host-http@1.0.2
22
+ - zhin.js@5.0.0
23
+
24
+ ## 2.0.3
25
+
26
+ ### Patch Changes
27
+
28
+ - cc5c94d: 约定式插件运行时迁移(breaking):插件与适配器由 `usePlugin()` / `extends Adapter` 迁移为 `definePlugin` / `defineAdapter` + `plugin.ts` + 约定目录(`adapters/`、`commands/`、`components/`、`tools/` 等)。
29
+
30
+ - 新增约定式运行时包:`@zhin.js/plugin-runtime`、`@zhin.js/adapter`、`@zhin.js/runtime`、`@zhin.js/host-http`(首版 1.0.0 走 init-publish,不在本 changeset 内 bump)。
31
+ - 全部 20 个平台适配器改为约定式 `defineAdapter`,旧 `usePlugin` / `extends Adapter` / `segment-mapper` 生产入口已删除;onebot11 反向 WSS、onebot12 webhook/wss、milky sse/webhook/wss、satori webhook、kook webhook、qq webhook/middleware 等 slice 1 推迟的连接模式已补齐。
32
+ - 游戏 / 工具 / 服务插件同步迁移到约定目录结构。
33
+ - CLI 增加 plugin-runtime host installer(http/database/outbound/schedule/console 等)。
34
+
35
+ 后续加固(同批):
36
+
37
+ - CLI:`zhin runtime start --daemon`(pidfile/崩溃拉起/风暴保护),orphan watchdog 防僵尸进程;legacy `zhin dev` / `zhin start` 已移除(含 `zhin restart`),`zhin stop` 兼容新 daemon。
38
+ - 安全:builtin 工具统一走 `security/policy-facade.ts` 的 `runToolPolicies`(声明式策略表,deny 优先);审计日志 close flush + 背压队列;`splitCompoundCommand` 引号感知、`extractCommandName` 去引号堵绕过。
39
+ - 日志:Logger 双堆栈修复、本地时区、`getLogger` 挂树(`setLevel` 递归生效)、第三方库(log4js/discord)桥接、启动人读总结。
40
+ - 结构:`plugins/games/shared` 迁为 `packages/game-kit`(`@zhin.js/game-kit`);死目录 `plugins/adapters/common` 删除。
41
+ - 脚手架:`create-zhin-app` / `zhin new` / scaffold-wizard 生成物改为 Plugin Runtime 形态(minimal-bot 同构,新配置格式)。
42
+ - Console:endpoint.list 真实名称与 phase、schema:get-all 按 instanceKey 映射、db:\* 接 DatabaseHost。
43
+
44
+ 注:按仓库发布惯例(见 1bb345dd2),本次 breaking 迁移统一使用 patch,避免 zhin.js 5.0 级联。
45
+
46
+ - Updated dependencies [16ec4e8]
47
+ - Updated dependencies [cc5c94d]
48
+ - Updated dependencies [447f3e2]
49
+ - @zhin.js/core@1.3.5
50
+ - @zhin.js/agent@1.0.4
51
+ - @zhin.js/host-http@1.0.1
52
+ - zhin.js@4.1.3
53
+ - @zhin.js/logger@1.0.75
54
+ - @zhin.js/plugin-runtime@1.0.1
55
+ - @zhin.js/adapter@1.0.1
56
+
3
57
  ## 2.0.2
4
58
 
5
59
  ### Patch Changes
package/README.md CHANGED
@@ -1,13 +1,31 @@
1
1
  # @zhin.js/adapter-line
2
2
 
3
- Zhin.js 适配器 — LINE Messaging API (Webhook 模式)
3
+ Zhin.js LINE Messaging API 适配器(Plugin Runtime),通过 Runtime Host HTTP Webhook 收发消息。
4
4
 
5
5
  ## 功能
6
6
 
7
- - 接收并解析 LINE Webhook 事件(text、image、video、audio、file、location、sticker)
8
- - 支持私聊、群组、多人聊天(room)三种场景
9
- - Reply API / Push API 发送消息
10
- - HMAC-SHA256 签名验证
7
+ - Webhook 事件接收(`httpHostToken` POST + HMAC-SHA256 签名验证)
8
+ - 解析 text / image / video / audio / file / location / sticker
9
+ - 支持私聊、群组、多人聊天(room)
10
+ - Reply API(有 replyToken 时)/ Push API 发送
11
+ - 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
12
+
13
+ ## 安装
14
+
15
+ ```bash
16
+ pnpm add @zhin.js/adapter-line
17
+ ```
18
+
19
+ ## Plugin Runtime
20
+
21
+ - `@zhin.js/adapter` — 约定式 `adapters/line.ts`(`defineAdapter`)
22
+ - `@zhin.js/core` — `messageGatewayToken` 入站/出站
23
+ - `@zhin.js/host-http` — `httpHostToken` 注册 Webhook 路由(**非** legacy host-router/Koa)
24
+ - `@zhin.js/plugin-runtime` — `plugin.ts`(`definePlugin`)
25
+ - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
26
+
27
+ 入站:`gateway.receive({ adapter, target: channelId, content: text, sender, metadata })`
28
+ 出站:`send({ target, payload })` → Reply API(缓存 replyToken)或 Push API
11
29
 
12
30
  ## 前置条件
13
31
 
@@ -15,20 +33,26 @@ Zhin.js 适配器 — LINE Messaging API (Webhook 模式)
15
33
  2. 获取 **Channel Secret** 和 **Channel Access Token**
16
34
  3. 设置 Webhook URL 为 `https://your-domain/line/webhook`
17
35
  4. 在 Console 中启用 **Use webhooks** 并关闭 **Auto-reply messages**
36
+ 5. Runtime Host(`http`)须已 listen,Webhook 才可达
37
+
38
+ 必填字段(`endpoints[i]`):`name`、`channelSecret`、`channelAccessToken`。
18
39
 
19
40
  ## 最小配置
20
41
 
21
42
  ```yaml
22
- # zhin.config.yml
23
- adapters:
24
- - context: line
25
- name: my-line-bot
26
- channelSecret: ${LINE_CHANNEL_SECRET}
27
- channelAccessToken: ${LINE_CHANNEL_ACCESS_TOKEN}
43
+ # zhin.config.yml(Plugin Runtime)
44
+ plugins:
45
+ line:
28
46
  webhookPath: /line/webhook # 可选,默认 /line/webhook
29
47
  apiBaseUrl: https://api.line.me # 可选,调试时可改为 LINE API 沙盒地址
48
+ endpoints:
49
+ - name: my-line-bot
50
+ channelSecret: ${LINE_CHANNEL_SECRET}
51
+ channelAccessToken: ${LINE_CHANNEL_ACCESS_TOKEN}
30
52
  ```
31
53
 
54
+ 根插件 `zhin.plugins`(或项目图)需引用 `@zhin.js/adapter-line`(`instanceKey: line`)。
55
+
32
56
  ## 环境变量
33
57
 
34
58
  | 变量 | 说明 |
@@ -40,7 +64,7 @@ adapters:
40
64
 
41
65
  LINE 要求 Webhook URL 以 HTTPS 开头。常见方案:
42
66
 
43
- - **反向代理**:Nginx/Caddy 将 `https://your-domain/line/webhook` 转发到本地 zhIn 端口
67
+ - **反向代理**:Nginx/Caddy 将 `https://your-domain/line/webhook` 转发到本地 zhin 端口
44
68
  - **Cloudflare Tunnel**:`cloudflared tunnel --url http://localhost:端口`
45
69
  - **ngrok**:调试用 `ngrok http 端口`
46
70
 
@@ -48,15 +72,15 @@ LINE 要求 Webhook URL 以 HTTPS 开头。常见方案:
48
72
 
49
73
  ## 消息类型映射
50
74
 
51
- | LINE 类型 | Zhin.js Segment | 说明 |
52
- |-----------|----------------|------|
53
- | text | text | 纯文本 |
54
- | image | image | 图片(暂不支持二次获取) |
55
- | video | video | 视频 |
56
- | audio | audio | 音频 |
57
- | file | file | 文件 |
58
- | location | location | 位置信息 |
59
- | sticker | sticker | 贴纸 |
75
+ | LINE 类型 | 入站 content(文本摘要) | 出站 wire |
76
+ |-----------|--------------------------|-----------|
77
+ | text | 原文 | text |
78
+ | image | `[image]` | image(需 `url`) |
79
+ | video | `[video]` | video(需 `url`) |
80
+ | audio | `[audio]` | audio(需 `url`) |
81
+ | file | `[file: name]` | |
82
+ | location | address 或坐标 | location |
83
+ | sticker | `[sticker: pkg/id]` | sticker |
60
84
 
61
85
  ## AI 工具
62
86
 
@@ -65,8 +89,6 @@ LINE 要求 Webhook URL 以 HTTPS 开头。常见方案:
65
89
  | Permit 词汇 | `agent/PERMITS.md` |
66
90
  | 平台工具(2 个) | `agent/tools/`(`line_get_profile`、`line_get_group_members`) |
67
91
  | 技能说明 | `agent/skills/line.md` |
68
- | 群管标准工具 | 覆写 `ISceneManagement` 后由 `createSceneManagementTools()` 注册 |
69
-
70
92
 
71
93
  ## 已知限制
72
94
 
@@ -79,7 +101,7 @@ LINE 要求 Webhook URL 以 HTTPS 开头。常见方案:
79
101
 
80
102
  | 问题 | 排查方法 |
81
103
  |------|---------|
82
- | Webhook Verify 失败 | 检查 HTTPS 证书、域名解析、端口是否可达 |
104
+ | Webhook Verify 失败 | 检查 HTTPS 证书、域名解析、端口是否可达;确认 host-http 已 listen |
83
105
  | 签名验证 403 | 确认 Channel Secret 与 Console 一致 |
84
106
  | 发送 401 | 确认 Channel Access Token 未过期 |
85
107
  | 发送 400 | 检查消息格式是否符合 LINE API 规范 |
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Convention entry: discover `adapters/line.ts` → defineAdapter.
3
+ */
4
+ import { defineAdapter } from '@zhin.js/adapter';
5
+ import { messageGatewayToken } from '@zhin.js/core/runtime';
6
+ import { httpHostToken } from '@zhin.js/host-http';
7
+ import { LineEndpoint } from '../src/endpoint.js';
8
+ import {
9
+ resolveLineConfig,
10
+ type LineAdapterConfig,
11
+ } from '../src/protocol.js';
12
+
13
+ export { LineEndpoint } from '../src/endpoint.js';
14
+ export type { LineEndpointOptions, LineFetch } from '../src/endpoint.js';
15
+
16
+ export default defineAdapter<LineAdapterConfig>({
17
+ capabilities: ['inbound', 'outbound'],
18
+ create(context) {
19
+ return new LineEndpoint({
20
+ id: context.id,
21
+ gateway: context.use(messageGatewayToken),
22
+ http: context.use(httpHostToken),
23
+ config: resolveLineConfig(context.config),
24
+ });
25
+ },
26
+ });
@@ -1,8 +1,8 @@
1
- import { defineTool } from '@zhin.js/agent/tools';
1
+ import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { getLineApiConfig } from '../../src/line-agent-deps.js';
4
4
 
5
- export default defineTool<{ groupId: string }>({
5
+ export default defineAgentTool<{ groupId: string }>({
6
6
  description: 'Get LINE group member IDs',
7
7
  inputSchema: z.object({
8
8
  groupId: z.string().min(1),
@@ -1,8 +1,8 @@
1
- import { defineTool } from '@zhin.js/agent/tools';
1
+ import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { getLineApiConfig } from '../../src/line-agent-deps.js';
4
4
 
5
- export default defineTool<{ userId: string }>({
5
+ export default defineAgentTool<{ userId: string }>({
6
6
  description: 'Get LINE user profile by userId',
7
7
  inputSchema: z.object({
8
8
  userId: z.string().min(1),
@@ -0,0 +1,46 @@
1
+ /**
2
+ * LineEndpoint — lifecycle, outbound, admit, OpenAPI helpers for agent tools.
3
+ */
4
+ import type { EndpointInstance } from '@zhin.js/adapter';
5
+ import type { MessageGateway } from '@zhin.js/core/runtime';
6
+ import type { HttpHost } from '@zhin.js/host-http';
7
+ import type { CapabilityId } from '@zhin.js/plugin-runtime';
8
+ import { type LineEvent, type ResolvedLineConfig } from './protocol.js';
9
+ export type LineFetch = (url: string, init?: {
10
+ readonly method?: string;
11
+ readonly headers?: Record<string, string>;
12
+ readonly body?: string;
13
+ }) => Promise<{
14
+ readonly ok: boolean;
15
+ readonly status: number;
16
+ text(): Promise<string>;
17
+ json(): Promise<unknown>;
18
+ }>;
19
+ export interface LineEndpointOptions {
20
+ readonly id: CapabilityId;
21
+ readonly gateway: MessageGateway;
22
+ readonly http: HttpHost;
23
+ readonly config: ResolvedLineConfig;
24
+ readonly fetch?: LineFetch;
25
+ }
26
+ export declare class LineEndpoint implements EndpointInstance {
27
+ #private;
28
+ constructor(options: LineEndpointOptions);
29
+ /** Used by webhook handler. */
30
+ get isOpen(): boolean;
31
+ get config(): ResolvedLineConfig;
32
+ getApiConfig(): {
33
+ accessToken: string;
34
+ apiBaseUrl: string;
35
+ };
36
+ start(): Promise<void>;
37
+ open(): void;
38
+ close(): void;
39
+ stop(): Promise<void>;
40
+ send({ target, payload }: {
41
+ readonly target: string;
42
+ readonly payload: unknown;
43
+ }): Promise<string>;
44
+ /** Test / internal: admit a parsed event when open (non-webhook path). */
45
+ admit(event: LineEvent): void;
46
+ }
@@ -0,0 +1,144 @@
1
+ import { formatCompact, getLogger } from '@zhin.js/logger';
2
+ import { registerLineAgentEndpoint } from './line-agent-deps.js';
3
+ import { formatInboundContent, formatOutboundMessages, generateMessageId, isMessageEvent, isValidLineRecipientId, resolveChannel, } from './protocol.js';
4
+ import { registerLineWebhookRoutes } from './webhook.js';
5
+ const logger = getLogger('line');
6
+ export class LineEndpoint {
7
+ #options;
8
+ #fetch;
9
+ #routeReleases = [];
10
+ #replyTokenCache = new Map();
11
+ #open = false;
12
+ #started = false;
13
+ #unregisterAgent;
14
+ constructor(options) {
15
+ this.#options = options;
16
+ this.#fetch = options.fetch ?? globalThis.fetch;
17
+ }
18
+ /** Used by webhook handler. */
19
+ get isOpen() {
20
+ return this.#open;
21
+ }
22
+ get config() {
23
+ return this.#options.config;
24
+ }
25
+ getApiConfig() {
26
+ return {
27
+ accessToken: this.#options.config.channelAccessToken,
28
+ apiBaseUrl: this.#options.config.apiBaseUrl,
29
+ };
30
+ }
31
+ async start() {
32
+ if (this.#started)
33
+ return;
34
+ this.#started = true;
35
+ try {
36
+ this.#unregisterAgent = registerLineAgentEndpoint(this.#options.config.name, this);
37
+ this.#routeReleases.push(...registerLineWebhookRoutes(this.#options.http, this));
38
+ logger.debug(formatCompact({
39
+ endpoint: this.#options.config.name,
40
+ op: 'webhook',
41
+ path: this.#options.config.webhookPath,
42
+ }));
43
+ }
44
+ catch (error) {
45
+ await this.stop();
46
+ logger.error('Failed to connect LINE endpoint:', error);
47
+ throw error;
48
+ }
49
+ }
50
+ open() {
51
+ this.#open = true;
52
+ }
53
+ close() {
54
+ this.#open = false;
55
+ }
56
+ async stop() {
57
+ this.#open = false;
58
+ this.#replyTokenCache.clear();
59
+ for (const release of this.#routeReleases.splice(0))
60
+ release();
61
+ this.#unregisterAgent?.();
62
+ this.#unregisterAgent = undefined;
63
+ this.#started = false;
64
+ logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
65
+ }
66
+ async send({ target, payload }) {
67
+ const messages = formatOutboundMessages(payload);
68
+ if (messages.length === 0) {
69
+ throw new Error('No valid LINE messages to send');
70
+ }
71
+ const replyToken = this.#replyTokenCache.get(target);
72
+ if (replyToken) {
73
+ this.#replyTokenCache.delete(target);
74
+ return this.#replyMessage(replyToken, messages);
75
+ }
76
+ if (!isValidLineRecipientId(target)) {
77
+ throw new Error(`Invalid LINE recipient ID "${target}": must start with U (user), G (group), or R (room)`);
78
+ }
79
+ return this.#pushMessage(target, messages);
80
+ }
81
+ /** Test / internal: admit a parsed event when open (non-webhook path). */
82
+ admit(event) {
83
+ if (!this.#open)
84
+ return;
85
+ const { channelId } = resolveChannel(event.source);
86
+ if ('replyToken' in event && typeof event.replyToken === 'string') {
87
+ this.#replyTokenCache.set(channelId, event.replyToken);
88
+ }
89
+ void this.#options.gateway.receive({
90
+ adapter: this.#options.id,
91
+ target: channelId,
92
+ content: formatInboundContent(event),
93
+ sender: event.source.userId || channelId,
94
+ id: generateMessageId(event),
95
+ metadata: Object.freeze({
96
+ eventType: event.type,
97
+ sourceType: event.source.type,
98
+ endpoint: this.#options.config.name,
99
+ timestamp: event.timestamp,
100
+ ...(isMessageEvent(event) ? { messageType: event.message.type } : {}),
101
+ }),
102
+ }).catch((err) => {
103
+ logger.warn(formatCompact({
104
+ op: 'line_gateway_receive_failed',
105
+ target: channelId,
106
+ error: err instanceof Error ? err.message : String(err),
107
+ }));
108
+ });
109
+ }
110
+ async #replyMessage(replyToken, messages) {
111
+ const url = `${this.#options.config.apiBaseUrl}/v2/bot/message/reply`;
112
+ const response = await this.#fetch(url, {
113
+ method: 'POST',
114
+ headers: {
115
+ 'Content-Type': 'application/json',
116
+ Authorization: `Bearer ${this.#options.config.channelAccessToken}`,
117
+ },
118
+ body: JSON.stringify({ replyToken, messages }),
119
+ });
120
+ if (!response.ok) {
121
+ const errorText = await response.text();
122
+ throw new Error(`LINE Reply API error ${response.status}: ${errorText}`);
123
+ }
124
+ const result = await response.json();
125
+ return result.sentMessages?.[0]?.id || `reply-${Date.now()}`;
126
+ }
127
+ async #pushMessage(to, messages) {
128
+ const url = `${this.#options.config.apiBaseUrl}/v2/bot/message/push`;
129
+ const response = await this.#fetch(url, {
130
+ method: 'POST',
131
+ headers: {
132
+ 'Content-Type': 'application/json',
133
+ Authorization: `Bearer ${this.#options.config.channelAccessToken}`,
134
+ },
135
+ body: JSON.stringify({ to, messages }),
136
+ });
137
+ if (!response.ok) {
138
+ const errorText = await response.text();
139
+ throw new Error(`LINE Push API error ${response.status}: ${errorText}`);
140
+ }
141
+ const result = await response.json();
142
+ return result.sentMessages?.[0]?.id || `push-${Date.now()}`;
143
+ }
144
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { formatInboundContent, formatOutboundMessages, generateMessageId, isMessageEvent, isPostbackEvent, isValidLineRecipientId, normalizeWebhookPath, readTextBody, resolveChannel, resolveLineConfig, verifySignature, type LineAdapterConfig, type LineApiResponse, type LineChannel, type LineEvent, type LineFollowEvent, type LineJoinEvent, type LineLeaveEvent, type LineMessage, type LineMessageEvent, type LinePostbackEvent, type LinePushRequest, type LineReplyMessage, type LineReplyRequest, type LineSource, type LineUnfollowEvent, type LineUser, type LineWebhookBody, type LineWireSegment, type ResolvedLineConfig, } from './protocol.js';
2
+ export { LineEndpoint, type LineEndpointOptions, type LineFetch, } from './endpoint.js';
3
+ export { registerLineWebhookRoutes, handleLineWebhookRequest, type LineWebhookHandler, } from './webhook.js';
4
+ export { getLineAgentDeps, getLineApiConfig, registerLineAgentEndpoint, setLineAgentDeps, type LineAgentDeps, type LineAgentEndpoint, } from './line-agent-deps.js';
package/lib/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { formatInboundContent, formatOutboundMessages, generateMessageId, isMessageEvent, isPostbackEvent, isValidLineRecipientId, normalizeWebhookPath, readTextBody, resolveChannel, resolveLineConfig, verifySignature, } from './protocol.js';
2
+ export { LineEndpoint, } from './endpoint.js';
3
+ export { registerLineWebhookRoutes, handleLineWebhookRequest, } from './webhook.js';
4
+ export { getLineAgentDeps, getLineApiConfig, registerLineAgentEndpoint, setLineAgentDeps, } from './line-agent-deps.js';
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Agent tool deps for line (get_profile / get_group_members).
3
+ * Endpoints register themselves on start; tools look up the active API config.
4
+ */
5
+ export interface LineAgentEndpoint {
6
+ getApiConfig(): {
7
+ accessToken: string;
8
+ apiBaseUrl: string;
9
+ };
10
+ }
11
+ export interface LineAgentDeps {
12
+ getApiConfig: () => {
13
+ accessToken: string;
14
+ apiBaseUrl: string;
15
+ };
16
+ }
17
+ export declare function registerLineAgentEndpoint(endpointId: string, endpoint: LineAgentEndpoint): () => void;
18
+ /** Optional override used by tests / transitional callers. Pass `null` to clear. */
19
+ export declare function setLineAgentDeps(deps: LineAgentDeps | null): void;
20
+ export declare function getLineAgentDeps(): LineAgentDeps;
21
+ export declare function getLineApiConfig(): {
22
+ accessToken: string;
23
+ apiBaseUrl: string;
24
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Agent tool deps for line (get_profile / get_group_members).
3
+ * Endpoints register themselves on start; tools look up the active API config.
4
+ */
5
+ const endpoints = new Map();
6
+ let override = null;
7
+ export function registerLineAgentEndpoint(endpointId, endpoint) {
8
+ endpoints.set(endpointId, endpoint);
9
+ return () => {
10
+ if (endpoints.get(endpointId) === endpoint) {
11
+ endpoints.delete(endpointId);
12
+ }
13
+ };
14
+ }
15
+ /** Optional override used by tests / transitional callers. Pass `null` to clear. */
16
+ export function setLineAgentDeps(deps) {
17
+ override = deps;
18
+ }
19
+ export function getLineAgentDeps() {
20
+ if (override)
21
+ return override;
22
+ return {
23
+ getApiConfig() {
24
+ const first = endpoints.values().next().value;
25
+ if (!first)
26
+ throw new Error('LINE channel access token not configured');
27
+ return first.getApiConfig();
28
+ },
29
+ };
30
+ }
31
+ export function getLineApiConfig() {
32
+ return getLineAgentDeps().getApiConfig();
33
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * LINE Messaging API protocol helpers — no legacy Adapter/Endpoint / segment-mapper.
3
+ * Canonicalization is owned by gateway/core before endpoint.send.
4
+ */
5
+ import type { IncomingMessage } from 'node:http';
6
+ /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
7
+ export interface LineAdapterConfig {
8
+ readonly name?: string;
9
+ readonly channelSecret?: string;
10
+ readonly channelAccessToken?: string;
11
+ readonly webhookPath?: string;
12
+ readonly apiBaseUrl?: string;
13
+ /** Transitional: legacy root `endpoints[]` with `context: line`. */
14
+ readonly endpoints?: ReadonlyArray<Partial<ResolvedLineConfig> & {
15
+ readonly context?: string;
16
+ }>;
17
+ }
18
+ export interface ResolvedLineConfig {
19
+ readonly context: 'line';
20
+ readonly name: string;
21
+ readonly channelSecret: string;
22
+ readonly channelAccessToken: string;
23
+ readonly webhookPath: string;
24
+ readonly apiBaseUrl: string;
25
+ }
26
+ export interface LineUser {
27
+ readonly userId: string;
28
+ readonly displayName?: string;
29
+ readonly pictureUrl?: string;
30
+ readonly statusMessage?: string;
31
+ }
32
+ export interface LineMessageEvent {
33
+ readonly type: 'message';
34
+ readonly replyToken: string;
35
+ readonly source: LineSource;
36
+ readonly timestamp: number;
37
+ readonly message: LineMessage;
38
+ }
39
+ export interface LineFollowEvent {
40
+ readonly type: 'follow';
41
+ readonly replyToken: string;
42
+ readonly source: LineSource;
43
+ readonly timestamp: number;
44
+ }
45
+ export interface LineUnfollowEvent {
46
+ readonly type: 'unfollow';
47
+ readonly source: LineSource;
48
+ readonly timestamp: number;
49
+ }
50
+ export interface LineJoinEvent {
51
+ readonly type: 'join';
52
+ readonly replyToken: string;
53
+ readonly source: LineSource;
54
+ readonly timestamp: number;
55
+ }
56
+ export interface LineLeaveEvent {
57
+ readonly type: 'leave';
58
+ readonly source: LineSource;
59
+ readonly timestamp: number;
60
+ }
61
+ export interface LinePostbackEvent {
62
+ readonly type: 'postback';
63
+ readonly replyToken: string;
64
+ readonly source: LineSource;
65
+ readonly timestamp: number;
66
+ readonly postback: {
67
+ readonly data: string;
68
+ readonly params?: Record<string, string>;
69
+ };
70
+ }
71
+ export type LineEvent = LineMessageEvent | LineFollowEvent | LineUnfollowEvent | LineJoinEvent | LineLeaveEvent | LinePostbackEvent;
72
+ export interface LineSource {
73
+ readonly type: 'user' | 'group' | 'room';
74
+ readonly userId?: string;
75
+ readonly groupId?: string;
76
+ readonly roomId?: string;
77
+ }
78
+ export interface LineMessage {
79
+ readonly id: string;
80
+ readonly type: 'text' | 'image' | 'video' | 'audio' | 'file' | 'location' | 'sticker';
81
+ readonly text?: string;
82
+ readonly fileName?: string;
83
+ readonly fileSize?: number;
84
+ readonly title?: string;
85
+ readonly address?: string;
86
+ readonly latitude?: number;
87
+ readonly longitude?: number;
88
+ readonly packageId?: string;
89
+ readonly stickerId?: string;
90
+ readonly stickerResourceType?: string;
91
+ readonly duration?: number;
92
+ }
93
+ export interface LineWebhookBody {
94
+ readonly destination: string;
95
+ readonly events: readonly LineEvent[];
96
+ }
97
+ export interface LineReplyMessage {
98
+ readonly type: 'text' | 'image' | 'video' | 'audio' | 'location' | 'sticker' | 'flex';
99
+ readonly text?: string;
100
+ readonly originalContentUrl?: string;
101
+ readonly previewImageUrl?: string;
102
+ readonly packageId?: string;
103
+ readonly stickerId?: string;
104
+ readonly title?: string;
105
+ readonly address?: string;
106
+ readonly latitude?: number;
107
+ readonly longitude?: number;
108
+ readonly duration?: number;
109
+ readonly altText?: string;
110
+ readonly contents?: Record<string, unknown>;
111
+ }
112
+ export interface LineReplyRequest {
113
+ readonly replyToken: string;
114
+ readonly messages: readonly LineReplyMessage[];
115
+ }
116
+ export interface LinePushRequest {
117
+ readonly to: string;
118
+ readonly messages: readonly LineReplyMessage[];
119
+ }
120
+ export interface LineApiResponse {
121
+ readonly sentMessages?: ReadonlyArray<{
122
+ readonly id: string;
123
+ readonly quoteToken?: string;
124
+ }>;
125
+ }
126
+ export interface LineWireSegment {
127
+ readonly type: string;
128
+ readonly data?: Record<string, unknown>;
129
+ }
130
+ export interface LineChannel {
131
+ readonly channelType: 'private' | 'group' | 'channel';
132
+ readonly channelId: string;
133
+ }
134
+ export declare function resolveLineConfig(config?: LineAdapterConfig): ResolvedLineConfig;
135
+ export declare function normalizeWebhookPath(path: string): string;
136
+ export declare function isMessageEvent(event: LineEvent): event is LineMessageEvent;
137
+ export declare function isPostbackEvent(event: LineEvent): event is LinePostbackEvent;
138
+ export declare function resolveChannel(source: LineSource): LineChannel;
139
+ export declare function generateMessageId(event: LineEvent): string;
140
+ /** Build inbound text for MessageGateway.receive. */
141
+ export declare function formatInboundContent(event: LineEvent): string;
142
+ export declare function verifySignature(channelSecret: string, body: string, signature: string): boolean;
143
+ export declare function isValidLineRecipientId(id: string): boolean;
144
+ /**
145
+ * Wire-encode an already-rendered outbound payload into LINE Reply/Push messages.
146
+ * Segment canonicalization is intentionally not done here.
147
+ */
148
+ export declare function formatOutboundMessages(payload: unknown): LineReplyMessage[];
149
+ export declare function readTextBody(request: IncomingMessage, options?: {
150
+ readonly limit?: number;
151
+ }): Promise<string>;