@zhin.js/adapter-wecom 4.0.14 → 5.0.1

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,41 @@
1
1
  # @zhin.js/adapter-wecom
2
2
 
3
+ ## 5.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 1fc78bc: Unify native platform Client access behind the literal `adapter` discriminant. Handlers infer both native events and Clients, while command, inbound/outbound middleware, and both Agent tool authoring surfaces expose the exact operation-scoped Client through a lazy `$client` getter. Definitions without `adapter` keep `$client` typed as `unknown`, and runtime dispatch rejects adapter mismatches before resolving the Client. Bundled platform tools now use this single path instead of model-provided endpoint ids and adapter-specific dependency wrappers. Every adapter registers one Client/EventMap contract, and protocol adapters including NapCat, Milky, OneBot and Satori now produce transport-independent Client objects rather than letting Endpoint instances impersonate Clients.
8
+ - Updated dependencies [e9c6a73]
9
+ - Updated dependencies [4e8117c]
10
+ - Updated dependencies [902fa35]
11
+ - Updated dependencies [54bfd6b]
12
+ - Updated dependencies [12025ee]
13
+ - Updated dependencies [09b14d6]
14
+ - Updated dependencies [1fc78bc]
15
+ - @zhin.js/agent@1.1.16
16
+ - @zhin.js/adapter@1.2.1
17
+ - @zhin.js/core@1.5.14
18
+ - @zhin.js/host-http@1.0.13
19
+ - @zhin.js/command@1.0.16
20
+ - @zhin.js/logger@1.0.77
21
+ - zhin.js@6.0.14
22
+ - @zhin.js/feature-kit@1.0.13
23
+ - @zhin.js/permission@1.0.4
24
+
25
+ ## 5.0.0
26
+
27
+ ### Patch Changes
28
+
29
+ - f2c532f: Expose exact per-Endpoint message operations through one validated Adapter capability model, route Core control calls through declared active capabilities, and connect existing recall, edit, reaction, and typing implementations across platform adapters.
30
+ - Updated dependencies [b10d058]
31
+ - Updated dependencies [f2c532f]
32
+ - Updated dependencies [3dbf990]
33
+ - @zhin.js/host-http@1.0.12
34
+ - @zhin.js/adapter@1.2.0
35
+ - @zhin.js/core@1.5.13
36
+ - @zhin.js/agent@1.1.15
37
+ - zhin.js@6.0.13
38
+
3
39
  ## 4.0.14
4
40
 
5
41
  ### Patch Changes
package/README.md CHANGED
@@ -18,7 +18,7 @@ pnpm add @zhin.js/adapter-wecom
18
18
  ## Plugin Runtime
19
19
 
20
20
  - `@zhin.js/adapter` — 约定式 `adapters/wecom.ts`(`defineAdapter`)
21
- - `@zhin.js/core` — `messageGatewayToken` 入站/出站
21
+ - `@zhin.js/core` — `Endpoint.emit(...)` 入站、`outboundMessageToken` 出站
22
22
  - `@zhin.js/host-http` — `httpHostToken` 注册 Webhook 路由(**非** legacy host-router/Koa)
23
23
  - `zhin.js` — `plugin.ts`(`definePlugin`)
24
24
  - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
package/adapters/wecom.js CHANGED
@@ -4,7 +4,6 @@
4
4
  * Implementation lives under `src/` (endpoint / webhook / protocol).
5
5
  */
6
6
  import { defineAdapter } from 'zhin.js/adapter';
7
- import { messageGatewayToken, sideEventGatewayToken } from '@zhin.js/core/runtime';
8
7
  import { httpHostToken } from '@zhin.js/host-http';
9
8
  import { WecomEndpoint } from "../lib/endpoint.js";
10
9
  import { resolveWecomConfig, } from "../lib/protocol.js";
@@ -12,6 +11,7 @@ import { wecomRuntimeStateToken } from "../lib/wecom-runtime-state.js";
12
11
  export { WecomEndpoint } from "../lib/endpoint.js";
13
12
  export default defineAdapter({
14
13
  capabilities: ['inbound', 'outbound'],
14
+ operations: ['recall'],
15
15
  // image 段全部经 /cgi-bin/media/upload 物化为 media_id(url 下载后上传、
16
16
  // base64/path 直接上传、file 引用直用 media_id);无卡片交互面,交互段降级纯文本。
17
17
  segments: {
@@ -27,8 +27,6 @@ export default defineAdapter({
27
27
  });
28
28
  return new WecomEndpoint({
29
29
  id: context.id,
30
- gateway: context.use(messageGatewayToken),
31
- sideEvents: context.use(sideEventGatewayToken),
32
30
  http: context.use(httpHostToken),
33
31
  config,
34
32
  });
package/adapters/wecom.ts CHANGED
@@ -3,7 +3,6 @@
3
3
  * Implementation lives under `src/` (endpoint / webhook / protocol).
4
4
  */
5
5
  import { defineAdapter } from 'zhin.js/adapter';
6
- import { messageGatewayToken, sideEventGatewayToken } from '@zhin.js/core/runtime';
7
6
  import { httpHostToken } from '@zhin.js/host-http';
8
7
  import { WecomEndpoint } from '../src/endpoint.js';
9
8
  import {
@@ -17,6 +16,7 @@ export type { WecomEndpointOptions, WecomFetch } from '../src/endpoint.js';
17
16
 
18
17
  export default defineAdapter<WecomAdapterConfig>({
19
18
  capabilities: ['inbound', 'outbound'],
19
+ operations: ['recall'],
20
20
  // image 段全部经 /cgi-bin/media/upload 物化为 media_id(url 下载后上传、
21
21
  // base64/path 直接上传、file 引用直用 media_id);无卡片交互面,交互段降级纯文本。
22
22
  segments: {
@@ -32,8 +32,6 @@ export default defineAdapter<WecomAdapterConfig>({
32
32
  });
33
33
  return new WecomEndpoint({
34
34
  id: context.id,
35
- gateway: context.use(messageGatewayToken),
36
- sideEvents: context.use(sideEventGatewayToken),
37
35
  http: context.use(httpHostToken),
38
36
  config,
39
37
  });
@@ -1,16 +1,14 @@
1
1
  import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
- import { getWecomAgentDeps } from '../../src/wecom-agent-deps.js';
4
- export default defineAgentTool<{ endpoint_id: string; dept_id: string }>({
3
+ export default defineAgentTool<{ dept_id: string }>({
5
4
  description: '获取企业微信部门用户列表',
6
5
  inputSchema: z.object({
7
- endpoint_id: z.string().describe('Endpoint 名称'),
8
6
  dept_id: z.string().describe('部门 ID'),
9
7
  }),
10
- platforms: ['wecom'],
8
+ adapter: 'wecom',
11
9
  tags: ['wecom'],
12
- async execute({ endpoint_id, dept_id }: { endpoint_id: string; dept_id: string }) {
13
- const endpoint = getWecomAgentDeps().getEndpoint(endpoint_id);
10
+ async execute({ dept_id }: { dept_id: string }, context) {
11
+ const endpoint = context.$client;
14
12
  const users = await endpoint.getDepartmentUsers(Number(dept_id));
15
13
  return { users, count: users.length };
16
14
  },
@@ -1,16 +1,14 @@
1
1
  import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
- import { getWecomAgentDeps } from '../../src/wecom-agent-deps.js';
4
- export default defineAgentTool<{ endpoint_id: string; user_id: string }>({
3
+ export default defineAgentTool<{ user_id: string }>({
5
4
  description: '获取企业微信用户信息',
6
5
  inputSchema: z.object({
7
- endpoint_id: z.string().describe('Endpoint 名称'),
8
6
  user_id: z.string().describe('用户 ID'),
9
7
  }),
10
- platforms: ['wecom'],
8
+ adapter: 'wecom',
11
9
  tags: ['wecom'],
12
- async execute({ endpoint_id, user_id }: { endpoint_id: string; user_id: string }) {
13
- const endpoint = getWecomAgentDeps().getEndpoint(endpoint_id);
10
+ async execute({ user_id }: { user_id: string }, context) {
11
+ const endpoint = context.$client;
14
12
  return await endpoint.getUserInfo(user_id);
15
13
  },
16
14
  });
@@ -1,16 +1,14 @@
1
1
  import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
- import { getWecomAgentDeps } from '../../src/wecom-agent-deps.js';
4
- export default defineAgentTool<{ endpoint_id: string; dept_id?: string }>({
3
+ export default defineAgentTool<{ dept_id?: string }>({
5
4
  description: '获取企业微信部门列表',
6
5
  inputSchema: z.object({
7
- endpoint_id: z.string().describe('Endpoint 名称'),
8
6
  dept_id: z.string().optional().describe('父部门 ID,默认 1(根部门)'),
9
7
  }),
10
- platforms: ['wecom'],
8
+ adapter: 'wecom',
11
9
  tags: ['wecom'],
12
- async execute({ endpoint_id, dept_id }: { endpoint_id: string; dept_id?: string }) {
13
- const endpoint = getWecomAgentDeps().getEndpoint(endpoint_id);
10
+ async execute({ dept_id }: { dept_id?: string }, context) {
11
+ const endpoint = context.$client;
14
12
  const departments = await endpoint.getDepartmentList(Number(dept_id) || 1);
15
13
  return { departments, count: departments.length };
16
14
  },
@@ -1,17 +1,15 @@
1
1
  import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
- import { getWecomAgentDeps } from '../../src/wecom-agent-deps.js';
4
- export default defineAgentTool<{ endpoint_id: string; user_id: string; content: string }>({
3
+ export default defineAgentTool<{ user_id: string; content: string }>({
5
4
  description: '向指定企业微信用户发送文本消息',
6
5
  inputSchema: z.object({
7
- endpoint_id: z.string().describe('Endpoint 名称'),
8
6
  user_id: z.string().describe('用户 ID'),
9
7
  content: z.string().describe('消息内容'),
10
8
  }),
11
- platforms: ['wecom'],
9
+ adapter: 'wecom',
12
10
  tags: ['wecom'],
13
- async execute({ endpoint_id, user_id, content }: { endpoint_id: string; user_id: string; content: string }) {
14
- const endpoint = getWecomAgentDeps().getEndpoint(endpoint_id);
11
+ async execute({ user_id, content }: { user_id: string; content: string }, context) {
12
+ const endpoint = context.$client;
15
13
  const success = await endpoint.sendTextMessage(user_id, content);
16
14
  return { success, message: success ? '消息已发送' : '发送失败' };
17
15
  },
@@ -0,0 +1,12 @@
1
+ import type { WecomClient } from './endpoint.js';
2
+ import type { WecomMessage } from './protocol.js';
3
+ export type WecomClientEventMap = Record<string, WecomMessage>;
4
+ declare module '@zhin.js/feature-kit' {
5
+ interface AdapterClientRegistry {
6
+ readonly wecom: {
7
+ readonly client: WecomClient;
8
+ readonly events: WecomClientEventMap;
9
+ };
10
+ }
11
+ }
12
+ export declare const wecomClient: import("@zhin.js/adapter").EndpointClientToken<WecomClient, WecomClientEventMap>;
package/lib/client.js ADDED
@@ -0,0 +1,2 @@
1
+ import { defineEndpointClient } from 'zhin.js/adapter';
2
+ export const wecomClient = defineEndpointClient('wecom');
package/lib/endpoint.d.ts CHANGED
@@ -1,11 +1,11 @@
1
+ import { Endpoint } from 'zhin.js/adapter';
1
2
  /**
2
3
  * WecomEndpoint — lifecycle, outbound send, inbound admit, OpenAPI helpers for agent tools.
3
4
  */
4
- import type { EndpointInstance, EndpointSendRequest } from 'zhin.js/adapter';
5
- import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
5
+ import { type EndpointControl, type EndpointSendRequest } from 'zhin.js/adapter';
6
6
  import type { HttpHost } from '@zhin.js/host-http';
7
7
  import type { CapabilityId } from 'zhin.js';
8
- import { type ResolvedWecomConfig, type WecomApiResponse, type WecomMessage } from './protocol.js';
8
+ import { type ResolvedWecomConfig, type WecomMessage } from './protocol.js';
9
9
  export type WecomFetch = (url: string, init?: {
10
10
  readonly method?: string;
11
11
  readonly headers?: Record<string, string>;
@@ -18,19 +18,34 @@ export type WecomFetch = (url: string, init?: {
18
18
  }>;
19
19
  export interface WecomEndpointOptions {
20
20
  readonly id: CapabilityId;
21
- readonly gateway: MessageGateway;
22
- readonly sideEvents?: SideEventGateway;
23
21
  readonly http: HttpHost;
24
22
  readonly config: ResolvedWecomConfig;
25
23
  readonly fetch?: WecomFetch;
26
24
  }
25
+ export interface WecomClientApi {
26
+ getUserInfo(userId: string): Promise<unknown>;
27
+ getDepartmentUsers(deptId: number): Promise<unknown[]>;
28
+ getDepartmentList(deptId?: number): Promise<unknown[]>;
29
+ sendTextMessage(userId: string, content: string): Promise<boolean>;
30
+ }
31
+ /** WeCom API surface exposed to plugin events without Endpoint lifecycle methods. */
32
+ export declare class WecomClient implements WecomClientApi {
33
+ readonly api: WecomClientApi;
34
+ constructor(api: WecomClientApi);
35
+ getUserInfo: (userId: string) => Promise<unknown>;
36
+ getDepartmentUsers: (deptId: number) => Promise<unknown[]>;
37
+ getDepartmentList: (deptId?: number) => Promise<unknown[]>;
38
+ sendTextMessage: (userId: string, content: string) => Promise<boolean>;
39
+ }
27
40
  /**
28
41
  * 企业微信服务端 API 无 bot 群列表/群成员列表接口(客户群接口属「客户联系」
29
42
  * 独立授权域,非 bot 社交面),好友/频道概念亦不存在;
30
43
  * 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
31
44
  */
32
- export declare class WecomEndpoint implements EndpointInstance {
45
+ export declare class WecomEndpoint extends Endpoint<WecomClient> {
33
46
  #private;
47
+ readonly client: WecomClient;
48
+ readonly control: EndpointControl;
34
49
  constructor(options: WecomEndpointOptions);
35
50
  /** Used by webhook handler. */
36
51
  get isOpen(): boolean;
@@ -43,8 +58,4 @@ export declare class WecomEndpoint implements EndpointInstance {
43
58
  recallMessage(messageId: string): Promise<void>;
44
59
  /** Test / internal: admit a parsed message when open (non-webhook path). */
45
60
  admit(msg: WecomMessage): void;
46
- getUserInfo(userId: string): Promise<WecomApiResponse | null>;
47
- getDepartmentUsers(deptId: number): Promise<unknown[]>;
48
- getDepartmentList(deptId?: number): Promise<unknown[]>;
49
- sendTextMessage(userId: string, content: string): Promise<boolean>;
50
61
  }
package/lib/endpoint.js CHANGED
@@ -1,25 +1,47 @@
1
+ import { Endpoint } from 'zhin.js/adapter';
2
+ /**
3
+ * WecomEndpoint — lifecycle, outbound send, inbound admit, OpenAPI helpers for agent tools.
4
+ */
5
+ import { createRecallEndpointControl, } from 'zhin.js/adapter';
1
6
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
2
- import { registerWecomAgentEndpoint } from './wecom-agent-deps.js';
3
7
  import { buildMediaUploadForm, readOutboundImageMedia, resolveMediaBinary, } from './media-upload.js';
4
8
  import { buildSendRequestBody, formatInboundContent, formatOutboundBody, resolveChatType, wecomInboundConversation, } from './protocol.js';
5
9
  import { registerWecomWebhookRoutes } from './webhook.js';
6
10
  import { receiveWecomSideEvent } from './side-event-dispatch.js';
11
+ /** WeCom API surface exposed to plugin events without Endpoint lifecycle methods. */
12
+ export class WecomClient {
13
+ api;
14
+ constructor(api) {
15
+ this.api = api;
16
+ }
17
+ getUserInfo = (userId) => this.api.getUserInfo(userId);
18
+ getDepartmentUsers = (deptId) => this.api.getDepartmentUsers(deptId);
19
+ getDepartmentList = (deptId) => this.api.getDepartmentList(deptId);
20
+ sendTextMessage = (userId, content) => this.api.sendTextMessage(userId, content);
21
+ }
7
22
  /**
8
23
  * 企业微信服务端 API 无 bot 群列表/群成员列表接口(客户群接口属「客户联系」
9
24
  * 独立授权域,非 bot 社交面),好友/频道概念亦不存在;
10
25
  * 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
11
26
  */
12
- export class WecomEndpoint {
27
+ export class WecomEndpoint extends Endpoint {
28
+ client = new WecomClient({
29
+ getUserInfo: (userId) => this.#getUserInfo(userId),
30
+ getDepartmentUsers: (deptId) => this.#getDepartmentUsers(deptId),
31
+ getDepartmentList: (deptId) => this.#getDepartmentList(deptId),
32
+ sendTextMessage: (userId, content) => this.#sendTextMessage(userId, content),
33
+ });
13
34
  #logger;
14
35
  #options;
36
+ control = createRecallEndpointControl((id) => this.recallMessage(id));
15
37
  #fetch;
16
38
  #routeReleases = [];
17
39
  #accessToken = { access_token: '', expires_in: 0, timestamp: 0 };
18
40
  #refreshPromise = null;
19
41
  #open = false;
20
42
  #started = false;
21
- #unregisterAgent;
22
43
  constructor(options) {
44
+ super();
23
45
  this.#logger = getAdapterLogger('wecom', options.config.id);
24
46
  this.#options = options;
25
47
  this.#fetch = options.fetch ?? globalThis.fetch;
@@ -37,7 +59,6 @@ export class WecomEndpoint {
37
59
  this.#started = true;
38
60
  try {
39
61
  await this.#refreshAccessToken();
40
- this.#unregisterAgent = registerWecomAgentEndpoint(this.#options.config.id, this);
41
62
  this.#routeReleases.push(...registerWecomWebhookRoutes(this.#options.http, this));
42
63
  this.#logger.debug(formatCompact({
43
64
  endpoint: this.#options.config.id,
@@ -61,8 +82,6 @@ export class WecomEndpoint {
61
82
  this.#open = false;
62
83
  for (const release of this.#routeReleases.splice(0))
63
84
  release();
64
- this.#unregisterAgent?.();
65
- this.#unregisterAgent = undefined;
66
85
  this.#started = false;
67
86
  this.#logger.debug(formatCompact({ op: 'disconnect' }));
68
87
  }
@@ -152,12 +171,19 @@ export class WecomEndpoint {
152
171
  admit(msg) {
153
172
  if (!this.#open)
154
173
  return;
155
- if (receiveWecomSideEvent(this.#options.sideEvents, String(this.#options.id), this.#options.config.id, msg, this.#logger)) {
174
+ void this.emitPlatform(msg.Event || msg.MsgType || 'event', msg).catch((error) => {
175
+ this.#logger.warn(formatCompact({
176
+ op: 'wecom_platform_event_failed',
177
+ event: msg.Event || msg.MsgType,
178
+ error: error instanceof Error ? error.message : String(error),
179
+ }));
180
+ });
181
+ if (receiveWecomSideEvent((name, payload) => this.emit(name, payload), String(this.#options.id), this.#options.config.id, msg, this.#logger)) {
156
182
  return;
157
183
  }
158
184
  const chatType = resolveChatType(msg.FromUserName);
159
185
  const conversation = wecomInboundConversation(String(this.#options.id), msg);
160
- void this.#options.gateway.receive({
186
+ void this.emit('message.receive', {
161
187
  conversation,
162
188
  message: { conversation, id: msg.MsgId || `${msg.CreateTime}` },
163
189
  content: formatInboundContent(msg),
@@ -178,7 +204,7 @@ export class WecomEndpoint {
178
204
  }));
179
205
  });
180
206
  }
181
- async getUserInfo(userId) {
207
+ async #getUserInfo(userId) {
182
208
  try {
183
209
  const data = await this.#request('/cgi-bin/user/get', {
184
210
  params: { userid: userId },
@@ -192,7 +218,7 @@ export class WecomEndpoint {
192
218
  return null;
193
219
  }
194
220
  }
195
- async getDepartmentUsers(deptId) {
221
+ async #getDepartmentUsers(deptId) {
196
222
  try {
197
223
  const data = await this.#request('/cgi-bin/user/simplelist', {
198
224
  params: { department_id: deptId },
@@ -206,7 +232,7 @@ export class WecomEndpoint {
206
232
  return [];
207
233
  }
208
234
  }
209
- async getDepartmentList(deptId = 1) {
235
+ async #getDepartmentList(deptId = 1) {
210
236
  try {
211
237
  const data = await this.#request('/cgi-bin/department/list', {
212
238
  params: { id: deptId },
@@ -220,7 +246,7 @@ export class WecomEndpoint {
220
246
  return [];
221
247
  }
222
248
  }
223
- async sendTextMessage(userId, content) {
249
+ async #sendTextMessage(userId, content) {
224
250
  try {
225
251
  const endpointKey = String(this.#options.id);
226
252
  await this.send({
package/lib/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { buildSendRequestBody, decryptMessage, extractEncryptFromXml, formatInboundContent, formatOutboundBody, getAesKey, normalizeEchostrParam, normalizeWebhookPath, parseXmlMessage, queryParam, readTextBody, resolveChatType, resolveWecomConfig, verifySignature, type AccessToken, type ResolvedWecomConfig, type WecomAdapterConfig, type WecomApiResponse, type WecomMessage, type WecomSendBody, type WecomWireSegment, } from './protocol.js';
2
- export { getWecomAgentDeps, registerWecomAgentEndpoint, setWecomAgentDeps, type WecomAgentDeps, type WecomAgentEndpoint, } from './wecom-agent-deps.js';
2
+ export { wecomClient, type WecomClientEventMap } from './client.js';
3
3
  export { checkWecomPlatformPermit, normalizeWecomSenderForPermit, platformPermit, wecomGroupPermitResolver, } from './platform-permit.js';
4
- export { WecomEndpoint, type WecomEndpointOptions, type WecomFetch, } from './endpoint.js';
4
+ export { WecomClient, WecomEndpoint, type WecomClientApi, type WecomEndpointOptions, type WecomFetch, } from './endpoint.js';
5
5
  export { buildMediaUploadForm, readOutboundImageMedia, resolveMediaBinary, type MediaBinary, type WecomMediaUploadResult, } from './media-upload.js';
6
6
  export { registerWecomWebhookRoutes, handleWecomVerificationRequest, handleWecomWebhookRequest, type WecomWebhookHandler, } from './webhook.js';
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { buildSendRequestBody, decryptMessage, extractEncryptFromXml, formatInboundContent, formatOutboundBody, getAesKey, normalizeEchostrParam, normalizeWebhookPath, parseXmlMessage, queryParam, readTextBody, resolveChatType, resolveWecomConfig, verifySignature, } from './protocol.js';
2
- export { getWecomAgentDeps, registerWecomAgentEndpoint, setWecomAgentDeps, } from './wecom-agent-deps.js';
2
+ export { wecomClient } from './client.js';
3
3
  export { checkWecomPlatformPermit, normalizeWecomSenderForPermit, platformPermit, wecomGroupPermitResolver, } from './platform-permit.js';
4
- export { WecomEndpoint, } from './endpoint.js';
4
+ export { WecomClient, WecomEndpoint, } from './endpoint.js';
5
5
  export { buildMediaUploadForm, readOutboundImageMedia, resolveMediaBinary, } from './media-upload.js';
6
6
  export { registerWecomWebhookRoutes, handleWecomVerificationRequest, handleWecomWebhookRequest, } from './webhook.js';
package/lib/protocol.d.ts CHANGED
@@ -84,7 +84,7 @@ export declare function verifySignature(token: string, timestamp: string, nonce:
84
84
  export declare function decryptMessage(encrypted: string, encodingAESKey: string, corpId: string): string | null;
85
85
  export declare function extractEncryptFromXml(xml: string): string | null;
86
86
  export declare function parseXmlMessage(xml: string): WecomMessage | null;
87
- /** Build inbound text for MessageGateway.receive. */
87
+ /** Build inbound text for OutboundMessageService.receive. */
88
88
  export declare function formatInboundContent(msg: WecomMessage): string;
89
89
  export declare function resolveChatType(fromUserName: string): 'group' | 'private';
90
90
  /**
package/lib/protocol.js CHANGED
@@ -128,7 +128,7 @@ export function parseXmlMessage(xml) {
128
128
  return null;
129
129
  }
130
130
  }
131
- /** Build inbound text for MessageGateway.receive. */
131
+ /** Build inbound text for OutboundMessageService.receive. */
132
132
  export function formatInboundContent(msg) {
133
133
  switch (msg.MsgType) {
134
134
  case 'text':
@@ -1,4 +1,4 @@
1
- import type { SideEventGateway } from '@zhin.js/core/runtime';
1
+ import type { EndpointEventEmitter } from 'zhin.js/adapter';
2
2
  import { type getAdapterLogger } from '@zhin.js/logger';
3
3
  import { type WecomMessage } from './protocol.js';
4
- export declare function receiveWecomSideEvent(sideEvents: SideEventGateway | undefined, endpointKey: string, configId: string, msg: WecomMessage, logger: ReturnType<typeof getAdapterLogger>): boolean;
4
+ export declare function receiveWecomSideEvent(emit: EndpointEventEmitter, endpointKey: string, configId: string, msg: WecomMessage, logger: ReturnType<typeof getAdapterLogger>): boolean;
@@ -13,12 +13,12 @@ function mapWecomEventParts(eventName) {
13
13
  return { scene_type: 'wecom', sub_type: eventName || 'unknown' };
14
14
  }
15
15
  }
16
- export function receiveWecomSideEvent(sideEvents, endpointKey, configId, msg, logger) {
17
- if (!sideEvents || msg.MsgType !== 'event')
16
+ export function receiveWecomSideEvent(emit, endpointKey, configId, msg, logger) {
17
+ if (msg.MsgType !== 'event')
18
18
  return false;
19
19
  const eventName = msg.Event ?? 'unknown';
20
20
  if (eventName === 'enter_agent') {
21
- void sideEvents.receiveSystem(buildSystem(msg, {
21
+ void emit('system.receive', buildSystem(msg, {
22
22
  $id: `wecom:enter_agent:${msg.FromUserName}:${msg.CreateTime ?? Date.now()}`,
23
23
  $adapter: 'wecom',
24
24
  $endpoint: configId,
@@ -41,7 +41,7 @@ export function receiveWecomSideEvent(sideEvents, endpointKey, configId, msg, lo
41
41
  if (!parts)
42
42
  return false;
43
43
  const sceneType = resolveChatType(msg.FromUserName);
44
- void sideEvents.receiveNotice(buildNotice(msg, {
44
+ void emit('notice.receive', buildNotice(msg, {
45
45
  $id: `wecom:${eventName}:${msg.FromUserName}:${msg.CreateTime ?? Date.now()}`,
46
46
  $adapter: 'wecom',
47
47
  $endpoint: configId,
@@ -1 +1 @@
1
- export declare const wecomEndpointCommands: import("@zhin.js/adapter").EndpointCommands<Readonly<import("@zhin.js/command").CommandDefinition<unknown, unknown, import("@zhin.js/command").CommandMessage>>>;
1
+ export declare const wecomEndpointCommands: import("@zhin.js/adapter").EndpointCommands<Readonly<import("@zhin.js/command").CommandDefinition<unknown, unknown, import("@zhin.js/command").CommandMessage, string | undefined>>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-wecom",
3
- "version": "4.0.14",
3
+ "version": "5.0.1",
4
4
  "description": "Zhin.js WeCom (企业微信) adapter for Plugin Runtime (HTTP webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -34,21 +34,22 @@
34
34
  "directory": "plugins/adapters/wecom"
35
35
  },
36
36
  "dependencies": {
37
- "@zhin.js/adapter": "1.1.11",
38
- "@zhin.js/core": "1.5.12",
39
- "@zhin.js/host-http": "1.0.11",
37
+ "@zhin.js/adapter": "1.2.1",
38
+ "@zhin.js/core": "1.5.14",
39
+ "@zhin.js/feature-kit": "1.0.13",
40
+ "@zhin.js/host-http": "1.0.13",
40
41
  "@zhin.js/im-contract": "1.0.4",
41
- "@zhin.js/logger": "1.0.76"
42
+ "@zhin.js/logger": "1.0.77"
42
43
  },
43
44
  "peerDependencies": {
44
45
  "zod": "^4.0.0",
45
- "@zhin.js/adapter": "1.1.11",
46
- "@zhin.js/agent": "1.1.14",
47
- "@zhin.js/command": "1.0.15",
48
- "@zhin.js/core": "1.5.12",
49
- "@zhin.js/host-http": "1.0.11",
50
- "@zhin.js/permission": "1.0.3",
51
- "zhin.js": "6.0.12"
46
+ "@zhin.js/adapter": "1.2.1",
47
+ "@zhin.js/agent": "1.1.16",
48
+ "@zhin.js/command": "1.0.16",
49
+ "@zhin.js/core": "1.5.14",
50
+ "@zhin.js/host-http": "1.0.13",
51
+ "@zhin.js/permission": "1.0.4",
52
+ "zhin.js": "6.0.14"
52
53
  },
53
54
  "peerDependenciesMeta": {
54
55
  "@zhin.js/agent": {
@@ -69,8 +70,8 @@
69
70
  "typescript": "^6.0.3",
70
71
  "vitest": "^4.1.10",
71
72
  "zod": "^4.4.3",
72
- "@zhin.js/agent": "1.1.14",
73
- "zhin.js": "6.0.12"
73
+ "@zhin.js/agent": "1.1.16",
74
+ "zhin.js": "6.0.14"
74
75
  },
75
76
  "files": [
76
77
  "adapters",
package/src/client.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { defineEndpointClient } from 'zhin.js/adapter';
2
+ import type { WecomClient } from './endpoint.js';
3
+ import type { WecomMessage } from './protocol.js';
4
+
5
+ export type WecomClientEventMap = Record<string, WecomMessage>;
6
+
7
+ declare module '@zhin.js/feature-kit' {
8
+ interface AdapterClientRegistry {
9
+ readonly wecom: {
10
+ readonly client: WecomClient;
11
+ readonly events: WecomClientEventMap;
12
+ };
13
+ }
14
+ }
15
+
16
+ export const wecomClient = defineEndpointClient<WecomClient, WecomClientEventMap>('wecom');
package/src/endpoint.ts CHANGED
@@ -1,12 +1,15 @@
1
+ import { Endpoint } from 'zhin.js/adapter';
1
2
  /**
2
3
  * WecomEndpoint — lifecycle, outbound send, inbound admit, OpenAPI helpers for agent tools.
3
4
  */
4
- import type { EndpointInstance, EndpointSendRequest } from 'zhin.js/adapter';
5
- import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
5
+ import {
6
+ createRecallEndpointControl,
7
+ type EndpointControl,
8
+ type EndpointSendRequest,
9
+ } from 'zhin.js/adapter';
6
10
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
7
11
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
8
12
  import type { CapabilityId } from 'zhin.js';
9
- import { registerWecomAgentEndpoint } from './wecom-agent-deps.js';
10
13
  import {
11
14
  buildMediaUploadForm,
12
15
  readOutboundImageMedia,
@@ -43,31 +46,52 @@ export type WecomFetch = (
43
46
 
44
47
  export interface WecomEndpointOptions {
45
48
  readonly id: CapabilityId;
46
- readonly gateway: MessageGateway;
47
- readonly sideEvents?: SideEventGateway;
48
49
  readonly http: HttpHost;
49
50
  readonly config: ResolvedWecomConfig;
50
51
  readonly fetch?: WecomFetch;
51
52
  }
52
53
 
54
+ export interface WecomClientApi {
55
+ getUserInfo(userId: string): Promise<unknown>;
56
+ getDepartmentUsers(deptId: number): Promise<unknown[]>;
57
+ getDepartmentList(deptId?: number): Promise<unknown[]>;
58
+ sendTextMessage(userId: string, content: string): Promise<boolean>;
59
+ }
60
+
61
+ /** WeCom API surface exposed to plugin events without Endpoint lifecycle methods. */
62
+ export class WecomClient implements WecomClientApi {
63
+ constructor(readonly api: WecomClientApi) {}
64
+ getUserInfo = (userId: string) => this.api.getUserInfo(userId);
65
+ getDepartmentUsers = (deptId: number) => this.api.getDepartmentUsers(deptId);
66
+ getDepartmentList = (deptId?: number) => this.api.getDepartmentList(deptId);
67
+ sendTextMessage = (userId: string, content: string) => this.api.sendTextMessage(userId, content);
68
+ }
69
+
53
70
  /**
54
71
  * 企业微信服务端 API 无 bot 群列表/群成员列表接口(客户群接口属「客户联系」
55
72
  * 独立授权域,非 bot 社交面),好友/频道概念亦不存在;
56
73
  * 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
57
74
  */
58
- export class WecomEndpoint implements EndpointInstance {
75
+ export class WecomEndpoint extends Endpoint<WecomClient> {
76
+ readonly client = new WecomClient({
77
+ getUserInfo: (userId) => this.#getUserInfo(userId),
78
+ getDepartmentUsers: (deptId) => this.#getDepartmentUsers(deptId),
79
+ getDepartmentList: (deptId) => this.#getDepartmentList(deptId),
80
+ sendTextMessage: (userId, content) => this.#sendTextMessage(userId, content),
81
+ });
59
82
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
60
83
 
61
84
  readonly #options: WecomEndpointOptions;
85
+ readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
62
86
  readonly #fetch: WecomFetch;
63
87
  #routeReleases: HttpRouteRegistration[] = [];
64
88
  #accessToken: AccessToken = { access_token: '', expires_in: 0, timestamp: 0 };
65
89
  #refreshPromise: Promise<string> | null = null;
66
90
  #open = false;
67
91
  #started = false;
68
- #unregisterAgent?: () => void;
69
92
 
70
93
  constructor(options: WecomEndpointOptions) {
94
+ super();
71
95
  this.#logger = getAdapterLogger('wecom', options.config.id);
72
96
  this.#options = options;
73
97
  this.#fetch = options.fetch ?? globalThis.fetch;
@@ -87,7 +111,6 @@ export class WecomEndpoint implements EndpointInstance {
87
111
  this.#started = true;
88
112
  try {
89
113
  await this.#refreshAccessToken();
90
- this.#unregisterAgent = registerWecomAgentEndpoint(this.#options.config.id, this);
91
114
  this.#routeReleases.push(...registerWecomWebhookRoutes(this.#options.http, this));
92
115
  this.#logger.debug(formatCompact({
93
116
  endpoint: this.#options.config.id,
@@ -112,8 +135,6 @@ export class WecomEndpoint implements EndpointInstance {
112
135
  async stop(): Promise<void> {
113
136
  this.#open = false;
114
137
  for (const release of this.#routeReleases.splice(0)) release();
115
- this.#unregisterAgent?.();
116
- this.#unregisterAgent = undefined;
117
138
  this.#started = false;
118
139
  this.#logger.debug(formatCompact({ op: 'disconnect' }));
119
140
  }
@@ -204,8 +225,15 @@ export class WecomEndpoint implements EndpointInstance {
204
225
  /** Test / internal: admit a parsed message when open (non-webhook path). */
205
226
  admit(msg: WecomMessage): void {
206
227
  if (!this.#open) return;
228
+ void this.emitPlatform(msg.Event || msg.MsgType || 'event', msg).catch((error) => {
229
+ this.#logger.warn(formatCompact({
230
+ op: 'wecom_platform_event_failed',
231
+ event: msg.Event || msg.MsgType,
232
+ error: error instanceof Error ? error.message : String(error),
233
+ }));
234
+ });
207
235
  if (receiveWecomSideEvent(
208
- this.#options.sideEvents,
236
+ (name, payload) => this.emit(name, payload),
209
237
  String(this.#options.id),
210
238
  this.#options.config.id,
211
239
  msg,
@@ -215,7 +243,7 @@ export class WecomEndpoint implements EndpointInstance {
215
243
  }
216
244
  const chatType = resolveChatType(msg.FromUserName);
217
245
  const conversation = wecomInboundConversation(String(this.#options.id), msg);
218
- void this.#options.gateway.receive({
246
+ void this.emit('message.receive', {
219
247
  conversation,
220
248
  message: { conversation, id: msg.MsgId || `${msg.CreateTime}` },
221
249
  content: formatInboundContent(msg),
@@ -237,7 +265,7 @@ export class WecomEndpoint implements EndpointInstance {
237
265
  });
238
266
  }
239
267
 
240
- async getUserInfo(userId: string): Promise<WecomApiResponse | null> {
268
+ async #getUserInfo(userId: string): Promise<WecomApiResponse | null> {
241
269
  try {
242
270
  const data = await this.#request('/cgi-bin/user/get', {
243
271
  params: { userid: userId },
@@ -250,7 +278,7 @@ export class WecomEndpoint implements EndpointInstance {
250
278
  }
251
279
  }
252
280
 
253
- async getDepartmentUsers(deptId: number): Promise<unknown[]> {
281
+ async #getDepartmentUsers(deptId: number): Promise<unknown[]> {
254
282
  try {
255
283
  const data = await this.#request('/cgi-bin/user/simplelist', {
256
284
  params: { department_id: deptId },
@@ -263,7 +291,7 @@ export class WecomEndpoint implements EndpointInstance {
263
291
  }
264
292
  }
265
293
 
266
- async getDepartmentList(deptId: number = 1): Promise<unknown[]> {
294
+ async #getDepartmentList(deptId: number = 1): Promise<unknown[]> {
267
295
  try {
268
296
  const data = await this.#request('/cgi-bin/department/list', {
269
297
  params: { id: deptId },
@@ -276,7 +304,7 @@ export class WecomEndpoint implements EndpointInstance {
276
304
  }
277
305
  }
278
306
 
279
- async sendTextMessage(userId: string, content: string): Promise<boolean> {
307
+ async #sendTextMessage(userId: string, content: string): Promise<boolean> {
280
308
  try {
281
309
  const endpointKey = String(this.#options.id);
282
310
  await this.send({
package/src/index.ts CHANGED
@@ -22,13 +22,7 @@ export {
22
22
  type WecomWireSegment,
23
23
  } from './protocol.js';
24
24
 
25
- export {
26
- getWecomAgentDeps,
27
- registerWecomAgentEndpoint,
28
- setWecomAgentDeps,
29
- type WecomAgentDeps,
30
- type WecomAgentEndpoint,
31
- } from './wecom-agent-deps.js';
25
+ export { wecomClient, type WecomClientEventMap } from './client.js';
32
26
 
33
27
  export {
34
28
  checkWecomPlatformPermit,
@@ -38,7 +32,9 @@ export {
38
32
  } from './platform-permit.js';
39
33
 
40
34
  export {
35
+ WecomClient,
41
36
  WecomEndpoint,
37
+ type WecomClientApi,
42
38
  type WecomEndpointOptions,
43
39
  type WecomFetch,
44
40
  } from './endpoint.js';
package/src/protocol.ts CHANGED
@@ -227,7 +227,7 @@ export function parseXmlMessage(xml: string): WecomMessage | null {
227
227
  }
228
228
  }
229
229
 
230
- /** Build inbound text for MessageGateway.receive. */
230
+ /** Build inbound text for OutboundMessageService.receive. */
231
231
  export function formatInboundContent(msg: WecomMessage): string {
232
232
  switch (msg.MsgType) {
233
233
  case 'text':
@@ -1,5 +1,5 @@
1
1
  import { buildNotice, buildSystem, senderFromId } from '@zhin.js/core';
2
- import type { SideEventGateway } from '@zhin.js/core/runtime';
2
+ import type { EndpointEventEmitter } from 'zhin.js/adapter';
3
3
  import { formatCompact, type getAdapterLogger } from '@zhin.js/logger';
4
4
  import { resolveChatType, type WecomMessage } from './protocol.js';
5
5
 
@@ -17,16 +17,16 @@ function mapWecomEventParts(eventName: string): { scene_type: string; sub_type:
17
17
  }
18
18
 
19
19
  export function receiveWecomSideEvent(
20
- sideEvents: SideEventGateway | undefined,
20
+ emit: EndpointEventEmitter,
21
21
  endpointKey: string,
22
22
  configId: string,
23
23
  msg: WecomMessage,
24
24
  logger: ReturnType<typeof getAdapterLogger>,
25
25
  ): boolean {
26
- if (!sideEvents || msg.MsgType !== 'event') return false;
26
+ if (msg.MsgType !== 'event') return false;
27
27
  const eventName = msg.Event ?? 'unknown';
28
28
  if (eventName === 'enter_agent') {
29
- void sideEvents.receiveSystem(buildSystem(msg, {
29
+ void emit('system.receive', buildSystem(msg, {
30
30
  $id: `wecom:enter_agent:${msg.FromUserName}:${msg.CreateTime ?? Date.now()}`,
31
31
  $adapter: 'wecom' as never,
32
32
  $endpoint: configId,
@@ -48,7 +48,7 @@ export function receiveWecomSideEvent(
48
48
  const parts = mapWecomEventParts(eventName);
49
49
  if (!parts) return false;
50
50
  const sceneType = resolveChatType(msg.FromUserName);
51
- void sideEvents.receiveNotice(buildNotice(msg, {
51
+ void emit('notice.receive', buildNotice(msg, {
52
52
  $id: `wecom:${eventName}:${msg.FromUserName}:${msg.CreateTime ?? Date.now()}`,
53
53
  $adapter: 'wecom' as never,
54
54
  $endpoint: configId,
@@ -1,17 +0,0 @@
1
- /**
2
- * Agent tool deps for wecom (get_user / departments / send_text).
3
- * Endpoints register themselves on start; tools look up by endpoint id.
4
- */
5
- export interface WecomAgentEndpoint {
6
- getUserInfo(userId: string): Promise<unknown>;
7
- getDepartmentUsers(deptId: number): Promise<unknown[]>;
8
- getDepartmentList(deptId?: number): Promise<unknown[]>;
9
- sendTextMessage(userId: string, content: string): Promise<boolean>;
10
- }
11
- export interface WecomAgentDeps {
12
- getEndpoint: (endpointKey: string) => WecomAgentEndpoint;
13
- }
14
- export declare function registerWecomAgentEndpoint(endpointKey: string, endpoint: WecomAgentEndpoint): () => void;
15
- /** Optional override used by tests / transitional callers. Pass `null` to clear. */
16
- export declare function setWecomAgentDeps(deps: WecomAgentDeps | null): void;
17
- export declare function getWecomAgentDeps(): WecomAgentDeps;
@@ -1,30 +0,0 @@
1
- /**
2
- * Agent tool deps for wecom (get_user / departments / send_text).
3
- * Endpoints register themselves on start; tools look up by endpoint id.
4
- */
5
- const endpoints = new Map();
6
- let override = null;
7
- export function registerWecomAgentEndpoint(endpointKey, endpoint) {
8
- endpoints.set(endpointKey, endpoint);
9
- return () => {
10
- if (endpoints.get(endpointKey) === endpoint) {
11
- endpoints.delete(endpointKey);
12
- }
13
- };
14
- }
15
- /** Optional override used by tests / transitional callers. Pass `null` to clear. */
16
- export function setWecomAgentDeps(deps) {
17
- override = deps;
18
- }
19
- export function getWecomAgentDeps() {
20
- if (override)
21
- return override;
22
- return {
23
- getEndpoint(endpointKey) {
24
- const endpoint = endpoints.get(endpointKey);
25
- if (!endpoint)
26
- throw new Error(`Endpoint ${endpointKey} 不存在`);
27
- return endpoint;
28
- },
29
- };
30
- }
@@ -1,46 +0,0 @@
1
- /**
2
- * Agent tool deps for wecom (get_user / departments / send_text).
3
- * Endpoints register themselves on start; tools look up by endpoint id.
4
- */
5
-
6
- export interface WecomAgentEndpoint {
7
- getUserInfo(userId: string): Promise<unknown>;
8
- getDepartmentUsers(deptId: number): Promise<unknown[]>;
9
- getDepartmentList(deptId?: number): Promise<unknown[]>;
10
- sendTextMessage(userId: string, content: string): Promise<boolean>;
11
- }
12
-
13
- export interface WecomAgentDeps {
14
- getEndpoint: (endpointKey: string) => WecomAgentEndpoint;
15
- }
16
-
17
- const endpoints = new Map<string, WecomAgentEndpoint>();
18
- let override: WecomAgentDeps | null = null;
19
-
20
- export function registerWecomAgentEndpoint(
21
- endpointKey: string,
22
- endpoint: WecomAgentEndpoint,
23
- ): () => void {
24
- endpoints.set(endpointKey, endpoint);
25
- return () => {
26
- if (endpoints.get(endpointKey) === endpoint) {
27
- endpoints.delete(endpointKey);
28
- }
29
- };
30
- }
31
-
32
- /** Optional override used by tests / transitional callers. Pass `null` to clear. */
33
- export function setWecomAgentDeps(deps: WecomAgentDeps | null): void {
34
- override = deps;
35
- }
36
-
37
- export function getWecomAgentDeps(): WecomAgentDeps {
38
- if (override) return override;
39
- return {
40
- getEndpoint(endpointKey) {
41
- const endpoint = endpoints.get(endpointKey);
42
- if (!endpoint) throw new Error(`Endpoint ${endpointKey} 不存在`);
43
- return endpoint;
44
- },
45
- };
46
- }