@zhin.js/adapter-discord 1.1.0 → 1.1.2

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 (47) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +7 -8
  3. package/adapters/{discord.js → discord/index.js} +6 -6
  4. package/adapters/{discord.ts → discord/index.ts} +9 -9
  5. package/agents/discord/agent.json +20 -0
  6. package/agents/discord/boundaries.md +3 -0
  7. package/agents/discord/conventions.md +3 -0
  8. package/agents/discord/skills/discord/SKILL.md +21 -0
  9. package/agents/discord/skills/discord/tools/add_role/index.js +24 -0
  10. package/{agent/tools/add_role.ts → agents/discord/skills/discord/tools/add_role/index.ts} +4 -4
  11. package/agents/discord/skills/discord/tools/create_thread/index.js +29 -0
  12. package/{agent/tools/create_thread.ts → agents/discord/skills/discord/tools/create_thread/index.ts} +4 -4
  13. package/agents/discord/skills/discord/tools/forum_post/index.js +33 -0
  14. package/{agent/tools/forum_post.ts → agents/discord/skills/discord/tools/forum_post/index.ts} +2 -2
  15. package/agents/discord/skills/discord/tools/list_roles/index.js +28 -0
  16. package/{agent/tools/list_roles.ts → agents/discord/skills/discord/tools/list_roles/index.ts} +4 -4
  17. package/agents/discord/skills/discord/tools/react/index.js +24 -0
  18. package/{agent/tools/react.ts → agents/discord/skills/discord/tools/react/index.ts} +2 -2
  19. package/agents/discord/skills/discord/tools/remove_role/index.js +24 -0
  20. package/{agent/tools/remove_role.ts → agents/discord/skills/discord/tools/remove_role/index.ts} +4 -4
  21. package/agents/discord/skills/discord/tools/send_embed/index.js +43 -0
  22. package/{agent/tools/send_embed.ts → agents/discord/skills/discord/tools/send_embed/index.ts} +2 -2
  23. package/agents/discord/system.md +3 -0
  24. package/commands/{endpoint/add/[id].js → discord/endpoint/add/[id]/index.js} +1 -1
  25. package/commands/discord/endpoint/add/[id]/index.ts +3 -0
  26. package/{lib/discord-endpoint-commands.js → commands/discord/endpoint/definition.js} +5 -4
  27. package/{src/discord-endpoint-commands.ts → commands/discord/endpoint/definition.ts} +4 -4
  28. package/commands/{endpoint/list.js → discord/endpoint/list/index.js} +1 -1
  29. package/commands/discord/endpoint/list/index.ts +3 -0
  30. package/commands/{endpoint/remove/[id].js → discord/endpoint/remove/[id]/index.js} +1 -1
  31. package/commands/discord/endpoint/remove/[id]/index.ts +3 -0
  32. package/lib/discord-runtime-state.js +1 -1
  33. package/lib/index.d.ts +2 -2
  34. package/lib/index.js +1 -1
  35. package/lib/protocol.d.ts +15 -32
  36. package/lib/protocol.js +20 -33
  37. package/package.json +32 -21
  38. package/schema.json +66 -2
  39. package/src/discord-runtime-state.ts +1 -1
  40. package/src/index.ts +3 -1
  41. package/src/protocol.ts +37 -64
  42. package/agent/skills/discord.md +0 -104
  43. package/commands/endpoint/add/[id].ts +0 -3
  44. package/commands/endpoint/list.ts +0 -3
  45. package/commands/endpoint/remove/[id].ts +0 -3
  46. package/lib/discord-endpoint-commands.d.ts +0 -1
  47. /package/{agent/PERMITS.md → PERMITS.md} +0 -0
@@ -1,12 +1,13 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
1
2
  /**
2
- * `discord.endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
3
- * commands/endpoint/ 下的 list / add / remove 直接默认导出这三项。
3
+ * `discord endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
4
+ * commands/discord/endpoint/ 下的 list / add / remove 直接默认导出这三项。
4
5
  * 注:applicationId / publicKey 为顶层共享字段(slash commands / interactions 用),
5
- * endpoints[i] 仅需 name + token(见 schema.json),故 add 只录入 token。
6
+ * endpoints[i] 仅需 id + token(见 schema.json),故 add 只录入 token。
6
7
  */
7
8
  import { createEndpointCommands } from 'zhin.js/adapter';
8
9
  import { defineCommand } from 'zhin.js/command';
9
- import { discordRuntimeStateToken } from './discord-runtime-state.js';
10
+ import { discordRuntimeStateToken } from "../../../lib/discord-runtime-state.js";
10
11
  export const discordEndpointCommands = createEndpointCommands({
11
12
  adapterKey: 'discord',
12
13
  adapterDisplayName: 'Discord',
@@ -1,12 +1,12 @@
1
1
  /**
2
- * `discord.endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
3
- * commands/endpoint/ 下的 list / add / remove 直接默认导出这三项。
2
+ * `discord endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
3
+ * commands/discord/endpoint/ 下的 list / add / remove 直接默认导出这三项。
4
4
  * 注:applicationId / publicKey 为顶层共享字段(slash commands / interactions 用),
5
- * endpoints[i] 仅需 name + token(见 schema.json),故 add 只录入 token。
5
+ * endpoints[i] 仅需 id + token(见 schema.json),故 add 只录入 token。
6
6
  */
7
7
  import { createEndpointCommands } from 'zhin.js/adapter';
8
8
  import { defineCommand } from 'zhin.js/command';
9
- import { discordRuntimeStateToken } from './discord-runtime-state.js';
9
+ import { discordRuntimeStateToken } from '../../../src/discord-runtime-state.js';
10
10
 
11
11
  export const discordEndpointCommands = createEndpointCommands({
12
12
  adapterKey: 'discord',
@@ -1,3 +1,3 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { discordEndpointCommands } from "../../lib/discord-endpoint-commands.js";
2
+ import { discordEndpointCommands } from '../definition.js';
3
3
  export default discordEndpointCommands.list;
@@ -0,0 +1,3 @@
1
+ import { discordEndpointCommands } from '../definition.js';
2
+
3
+ export default discordEndpointCommands.list;
@@ -1,3 +1,3 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { discordEndpointCommands } from "../../../lib/discord-endpoint-commands.js";
2
+ import { discordEndpointCommands } from '../../definition.js';
3
3
  export default discordEndpointCommands.remove;
@@ -0,0 +1,3 @@
1
+ import { discordEndpointCommands } from '../../definition.js';
2
+
3
+ export default discordEndpointCommands.remove;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Discord 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
3
- * 由 plugin.ts setup() provide,adapter create 与 `discord.endpoint` 命令共享(同一 owner generation)。
3
+ * 由 plugin.ts setup() provide,adapter create 与 `discord endpoint` 命令共享(同一 owner generation)。
4
4
  */
5
5
  import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
6
6
  export const discordRuntimeStateToken = defineEndpointRuntimeStateToken('discord');
package/lib/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { activityTypeCode, formatButtonContent, formatInboundContent, formatOutboundBody, resolveChannelKind, resolveDiscordConfig, senderDisplayName, type DiscordAdapterConfig, type DiscordButtonInbound, type DiscordInboundAttachment, type DiscordInboundMessage, type DiscordOutboundBody, type DiscordWireSegment, type ResolvedDiscordConfig, type ResolvedDiscordGatewayConfig, type ResolvedDiscordInteractionsConfig, } from './protocol.js';
2
- export { discordClient, type DiscordClient, type DiscordClientEventMap, } from './client.js';
1
+ export { activityTypeCode, formatButtonContent, formatInboundContent, formatOutboundBody, resolveChannelKind, resolveDiscordConfig, senderDisplayName, type DiscordActivity, type DiscordEndpointConfig, type DiscordButtonInbound, type DiscordInboundAttachment, type DiscordInboundMessage, type DiscordOutboundBody, type DiscordWireSegment, type ResolvedDiscordConfig, type ResolvedDiscordGatewayConfig, type ResolvedDiscordInteractionsConfig, } from './protocol.js';
2
+ export { discordClient, requireDiscordGatewayClient, type DiscordClient, type DiscordClientEventMap, } from './client.js';
3
3
  export { checkDiscordPlatformPermit, discordGroupPermitResolver, normalizeDiscordSenderForPermit, platformPermit, } from './platform-permit.js';
4
4
  export { DiscordGatewayEndpoint, DiscordInteractionsEndpoint, type CreateDiscordClient, type DiscordClientTransport, type DiscordEndpointOptions, type DiscordInteractionsEndpointOptions, } from './endpoint.js';
5
5
  export { connectDiscordGatewayClient, defaultCreateClient, normalizeDiscordMessage, resolveSenderRole, toMessageCreateOptions, type DiscordGatewayConnectHandlers, } from './gateway.js';
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { activityTypeCode, formatButtonContent, formatInboundContent, formatOutboundBody, resolveChannelKind, resolveDiscordConfig, senderDisplayName, } from './protocol.js';
2
- export { discordClient, } from './client.js';
2
+ export { discordClient, requireDiscordGatewayClient, } from './client.js';
3
3
  export { checkDiscordPlatformPermit, discordGroupPermitResolver, normalizeDiscordSenderForPermit, platformPermit, } from './platform-permit.js';
4
4
  export { DiscordGatewayEndpoint, DiscordInteractionsEndpoint, } from './endpoint.js';
5
5
  export { connectDiscordGatewayClient, defaultCreateClient, normalizeDiscordMessage, resolveSenderRole, toMessageCreateOptions, } from './gateway.js';
package/lib/protocol.d.ts CHANGED
@@ -1,43 +1,26 @@
1
- /**
2
- * Discord Gateway protocol helpers — no legacy Adapter/Endpoint / segment-mapper.
3
- * Canonicalization is owned by gateway/core before endpoint.send.
4
- */
1
+ /** Discord Gateway protocol helpers. Canonicalization is owned by Core. */
2
+ import { type ConversationRef } from '@zhin.js/im-contract';
5
3
  import type { Segment } from '@zhin.js/core/runtime';
6
- import type { ConversationRef } from '@zhin.js/im-contract';
7
- /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
8
- export interface DiscordAdapterConfig {
9
- readonly id?: string;
10
- readonly token?: string;
4
+ export interface DiscordActivity {
5
+ readonly name: string;
6
+ readonly type: 'PLAYING' | 'STREAMING' | 'LISTENING' | 'WATCHING' | 'COMPETING';
7
+ readonly url?: string;
8
+ }
9
+ /** One endpoint config after AdapterIndex expands `plugins.<instanceKey>.endpoints`. */
10
+ export interface DiscordEndpointConfig {
11
+ readonly id: string;
12
+ readonly token: string;
11
13
  /** Default `gateway`. `interactions` uses httpHostToken POST + Ed25519 verify. */
12
14
  readonly connection?: 'gateway' | 'interactions';
13
15
  readonly intents?: readonly number[];
14
16
  readonly enableSlashCommands?: boolean;
15
17
  readonly globalCommands?: boolean;
16
- readonly defaultActivity?: {
17
- readonly name: string;
18
- readonly type: 'PLAYING' | 'STREAMING' | 'LISTENING' | 'WATCHING' | 'COMPETING';
19
- readonly url?: string;
20
- };
18
+ readonly defaultActivity?: DiscordActivity;
21
19
  readonly slashCommands?: readonly Record<string, unknown>[];
22
20
  /** Interactions-only fields. */
23
21
  readonly applicationId?: string;
24
22
  readonly publicKey?: string;
25
23
  readonly interactionsPath?: string;
26
- /** Transitional: legacy root `endpoints[]` with `context: discord`. */
27
- readonly endpoints?: ReadonlyArray<{
28
- readonly context?: string;
29
- readonly id?: string;
30
- readonly token?: string;
31
- readonly connection?: 'gateway' | 'interactions';
32
- readonly intents?: readonly number[];
33
- readonly enableSlashCommands?: boolean;
34
- readonly globalCommands?: boolean;
35
- readonly defaultActivity?: DiscordAdapterConfig['defaultActivity'];
36
- readonly slashCommands?: readonly Record<string, unknown>[];
37
- readonly applicationId?: string;
38
- readonly publicKey?: string;
39
- readonly interactionsPath?: string;
40
- }>;
41
24
  }
42
25
  export interface ResolvedDiscordGatewayConfig {
43
26
  readonly context: 'discord';
@@ -47,7 +30,7 @@ export interface ResolvedDiscordGatewayConfig {
47
30
  readonly intents?: readonly number[];
48
31
  readonly enableSlashCommands: boolean;
49
32
  readonly globalCommands: boolean;
50
- readonly defaultActivity?: DiscordAdapterConfig['defaultActivity'];
33
+ readonly defaultActivity?: DiscordActivity;
51
34
  readonly slashCommands?: readonly Record<string, unknown>[];
52
35
  }
53
36
  export interface ResolvedDiscordInteractionsConfig {
@@ -121,7 +104,7 @@ export interface DiscordOutboundBody {
121
104
  }>;
122
105
  readonly components?: ReadonlyArray<DiscordOutboundActionRow>;
123
106
  }
124
- export declare function resolveDiscordConfig(config?: DiscordAdapterConfig): ResolvedDiscordConfig;
107
+ export declare function resolveDiscordConfig(config: DiscordEndpointConfig): ResolvedDiscordConfig;
125
108
  export declare function resolveChannelKind(channelType: number | string | undefined): 'private' | 'group' | 'channel';
126
109
  /**
127
110
  * 入站归一化 → ConversationRef:DM → 'private';GroupDM → 'group';
@@ -154,7 +137,7 @@ export declare function formatButtonSegments(interaction: DiscordButtonInbound):
154
137
  * Segment canonicalization is intentionally not done here.
155
138
  */
156
139
  export declare function formatOutboundBody(payload: unknown): DiscordOutboundBody;
157
- export declare function activityTypeCode(type: NonNullable<DiscordAdapterConfig['defaultActivity']>['type']): number;
140
+ export declare function activityTypeCode(type: DiscordActivity['type']): number;
158
141
  /** interactions 验签时间戳时效窗口(防重放)。 */
159
142
  export declare const DISCORD_SIGNATURE_MAX_AGE_MS: number;
160
143
  export declare function verifyDiscordInteractionSignature(publicKeyHex: string, body: string, signature: string, timestamp: string, nowMs?: number): boolean;
package/lib/protocol.js CHANGED
@@ -1,33 +1,15 @@
1
- /**
2
- * Discord Gateway protocol helpers — no legacy Adapter/Endpoint / segment-mapper.
3
- * Canonicalization is owned by gateway/core before endpoint.send.
4
- */
1
+ /** Discord Gateway protocol helpers. Canonicalization is owned by Core. */
5
2
  import { createPublicKey, verify as cryptoVerify } from 'node:crypto';
6
- import { isMediaRef } from '@zhin.js/core';
3
+ import { isMediaRef } from '@zhin.js/im-contract';
7
4
  import { formatCompact, getLogger } from '@zhin.js/logger';
8
5
  const logger = getLogger('discord');
9
- export function resolveDiscordConfig(config = {}) {
10
- const entry = config.endpoints?.find((item) => item.context === 'discord' || !item.context);
11
- const token = (typeof config.token === 'string' && config.token)
12
- || (typeof entry?.token === 'string' && entry.token)
13
- || process.env.DISCORD_BOT_TOKEN
14
- || '';
15
- if (!token) {
16
- throw new TypeError('Discord adapter requires token (plugins.<key>.token or endpoints with context: discord)');
17
- }
18
- const id = (typeof config.id === 'string' && config.id)
19
- || (typeof entry?.id === 'string' && entry.id)
20
- || process.env.DISCORD_BOT_NAME
21
- || 'discord-bot';
22
- const connection = config.connection
23
- ?? entry?.connection
24
- ?? 'gateway';
6
+ export function resolveDiscordConfig(config) {
7
+ const id = requiredEndpointField(config.id, 'id');
8
+ const token = requiredEndpointField(config.token, 'token');
9
+ const connection = config.connection ?? 'gateway';
25
10
  if (connection === 'interactions') {
26
- const applicationId = config.applicationId || entry?.applicationId || '';
27
- const publicKey = config.publicKey || entry?.publicKey || '';
28
- if (!applicationId || !publicKey) {
29
- throw new TypeError('Discord connection:interactions requires applicationId and publicKey');
30
- }
11
+ const applicationId = requiredEndpointField(config.applicationId, 'applicationId');
12
+ const publicKey = requiredEndpointField(config.publicKey, 'publicKey');
31
13
  return {
32
14
  context: 'discord',
33
15
  connection: 'interactions',
@@ -35,7 +17,7 @@ export function resolveDiscordConfig(config = {}) {
35
17
  token,
36
18
  applicationId,
37
19
  publicKey,
38
- interactionsPath: config.interactionsPath || entry?.interactionsPath || '/discord/interactions',
20
+ interactionsPath: config.interactionsPath || '/discord/interactions',
39
21
  };
40
22
  }
41
23
  return {
@@ -43,14 +25,19 @@ export function resolveDiscordConfig(config = {}) {
43
25
  connection: 'gateway',
44
26
  id,
45
27
  token,
46
- intents: config.intents ?? entry?.intents,
47
- enableSlashCommands: config.enableSlashCommands === true
48
- || entry?.enableSlashCommands === true,
49
- globalCommands: config.globalCommands === true || entry?.globalCommands === true,
50
- defaultActivity: config.defaultActivity ?? entry?.defaultActivity,
51
- slashCommands: config.slashCommands ?? entry?.slashCommands,
28
+ intents: config.intents,
29
+ enableSlashCommands: config.enableSlashCommands === true,
30
+ globalCommands: config.globalCommands === true,
31
+ defaultActivity: config.defaultActivity,
32
+ slashCommands: config.slashCommands,
52
33
  };
53
34
  }
35
+ function requiredEndpointField(value, field) {
36
+ if (typeof value !== 'string' || !value.trim()) {
37
+ throw new TypeError(`Discord endpoint requires a non-empty ${field}`);
38
+ }
39
+ return value.trim();
40
+ }
54
41
  export function resolveChannelKind(channelType) {
55
42
  // discord.js ChannelType.DM = 1, GroupDM = 3
56
43
  if (channelType === 1 || channelType === 'DM' || channelType === 'private')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-discord",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Zhin.js Discord adapter for Plugin Runtime (Gateway WebSocket)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -33,26 +33,25 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "discord.js": "^14.27.0",
36
- "@zhin.js/adapter": "1.1.12",
37
- "@zhin.js/core": "1.1.35",
38
- "@zhin.js/feature-kit": "1.1.0",
39
- "@zhin.js/host-http": "1.1.0",
40
- "@zhin.js/im-contract": "1.1.0",
41
- "@zhin.js/logger": "1.1.0",
42
- "@zhin.js/permission": "1.1.0"
36
+ "@zhin.js/adapter": "1.1.14",
37
+ "@zhin.js/agent-feature": "1.1.1",
38
+ "@zhin.js/core": "1.1.37",
39
+ "@zhin.js/feature-kit": "1.1.1",
40
+ "@zhin.js/host-http": "1.1.1",
41
+ "@zhin.js/im-contract": "1.1.1",
42
+ "@zhin.js/logger": "1.1.1",
43
+ "@zhin.js/permission": "1.1.1",
44
+ "@zhin.js/skill": "1.1.1",
45
+ "@zhin.js/tool": "1.1.1"
43
46
  },
44
47
  "peerDependencies": {
45
48
  "zod": "^4.0.0",
46
- "@zhin.js/adapter": "^1.1.12",
47
- "@zhin.js/agent": "^1.1.23",
48
- "@zhin.js/command": "^1.1.0",
49
- "@zhin.js/core": "^1.1.35",
50
- "zhin.js": "^1.1.0"
49
+ "@zhin.js/adapter": "^1.1.14",
50
+ "@zhin.js/command": "^1.1.1",
51
+ "@zhin.js/core": "^1.1.37",
52
+ "zhin.js": "^1.1.2"
51
53
  },
52
54
  "peerDependenciesMeta": {
53
- "@zhin.js/agent": {
54
- "optional": true
55
- },
56
55
  "@zhin.js/command": {
57
56
  "optional": true
58
57
  },
@@ -68,9 +67,8 @@
68
67
  "typescript": "^6.0.3",
69
68
  "vitest": "^4.1.10",
70
69
  "zod": "^4.4.3",
71
- "@zhin.js/agent": "1.1.23",
72
- "@zhin.js/host-http": "1.1.0",
73
- "zhin.js": "1.1.0"
70
+ "@zhin.js/host-http": "1.1.1",
71
+ "zhin.js": "1.1.2"
74
72
  },
75
73
  "files": [
76
74
  "adapters",
@@ -79,9 +77,10 @@
79
77
  "schema.json",
80
78
  "src",
81
79
  "lib",
82
- "agent",
80
+ "agents",
83
81
  "README.md",
84
- "CHANGELOG.md"
82
+ "CHANGELOG.md",
83
+ "PERMITS.md"
85
84
  ],
86
85
  "publishConfig": {
87
86
  "access": "public",
@@ -104,6 +103,18 @@
104
103
  {
105
104
  "package": "@zhin.js/command",
106
105
  "api": "^1.0.0"
106
+ },
107
+ {
108
+ "package": "@zhin.js/tool",
109
+ "api": "^1.0.0"
110
+ },
111
+ {
112
+ "package": "@zhin.js/skill",
113
+ "api": "^1.0.0"
114
+ },
115
+ {
116
+ "package": "@zhin.js/agent-feature",
117
+ "api": "^1.0.0"
107
118
  }
108
119
  ],
109
120
  "plugins": []
package/schema.json CHANGED
@@ -91,10 +91,11 @@
91
91
  },
92
92
  "endpoints": {
93
93
  "type": "array",
94
- "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
94
+ "minItems": 1,
95
+ "description": "多账号:每项定义一个 endpoint,id/token 必填,其余字段覆盖实例默认值",
95
96
  "items": {
96
97
  "type": "object",
97
- "additionalProperties": true,
98
+ "additionalProperties": false,
98
99
  "properties": {
99
100
  "master": {
100
101
  "type": [
@@ -118,6 +119,69 @@
118
119
  "type": "string",
119
120
  "description": "Discord bot token"
120
121
  },
122
+ "connection": {
123
+ "type": "string",
124
+ "enum": [
125
+ "gateway",
126
+ "interactions"
127
+ ]
128
+ },
129
+ "intents": {
130
+ "type": "array",
131
+ "items": {
132
+ "type": "number"
133
+ }
134
+ },
135
+ "enableSlashCommands": {
136
+ "type": "boolean"
137
+ },
138
+ "globalCommands": {
139
+ "type": "boolean"
140
+ },
141
+ "defaultActivity": {
142
+ "type": "object",
143
+ "additionalProperties": false,
144
+ "properties": {
145
+ "name": {
146
+ "type": "string"
147
+ },
148
+ "type": {
149
+ "type": "string",
150
+ "enum": [
151
+ "PLAYING",
152
+ "STREAMING",
153
+ "LISTENING",
154
+ "WATCHING",
155
+ "COMPETING"
156
+ ]
157
+ },
158
+ "url": {
159
+ "type": "string"
160
+ }
161
+ },
162
+ "required": [
163
+ "name",
164
+ "type"
165
+ ]
166
+ },
167
+ "slashCommands": {
168
+ "type": "array",
169
+ "items": {
170
+ "type": "object"
171
+ }
172
+ },
173
+ "applicationId": {
174
+ "type": "string"
175
+ },
176
+ "publicKey": {
177
+ "type": "string"
178
+ },
179
+ "interactionsPath": {
180
+ "type": "string"
181
+ },
182
+ "commandPrefix": {
183
+ "type": "string"
184
+ },
121
185
  "id": {
122
186
  "type": "string",
123
187
  "description": "Discord bot name"
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Discord 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
3
- * 由 plugin.ts setup() provide,adapter create 与 `discord.endpoint` 命令共享(同一 owner generation)。
3
+ * 由 plugin.ts setup() provide,adapter create 与 `discord endpoint` 命令共享(同一 owner generation)。
4
4
  */
5
5
  import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
6
6
 
package/src/index.ts CHANGED
@@ -6,7 +6,8 @@ export {
6
6
  resolveChannelKind,
7
7
  resolveDiscordConfig,
8
8
  senderDisplayName,
9
- type DiscordAdapterConfig,
9
+ type DiscordActivity,
10
+ type DiscordEndpointConfig,
10
11
  type DiscordButtonInbound,
11
12
  type DiscordInboundAttachment,
12
13
  type DiscordInboundMessage,
@@ -19,6 +20,7 @@ export {
19
20
 
20
21
  export {
21
22
  discordClient,
23
+ requireDiscordGatewayClient,
22
24
  type DiscordClient,
23
25
  type DiscordClientEventMap,
24
26
  } from './client.js';
package/src/protocol.ts CHANGED
@@ -1,50 +1,33 @@
1
- /**
2
- * Discord Gateway protocol helpers — no legacy Adapter/Endpoint / segment-mapper.
3
- * Canonicalization is owned by gateway/core before endpoint.send.
4
- */
1
+ /** Discord Gateway protocol helpers. Canonicalization is owned by Core. */
5
2
 
6
3
  import { createPublicKey, verify as cryptoVerify } from 'node:crypto';
7
- import { isMediaRef } from '@zhin.js/core';
4
+ import { isMediaRef, type ConversationRef } from '@zhin.js/im-contract';
8
5
  import type { Segment } from '@zhin.js/core/runtime';
9
- import type { ConversationRef } from '@zhin.js/im-contract';
10
6
  import { formatCompact, getLogger } from '@zhin.js/logger';
11
7
 
12
8
  const logger = getLogger('discord');
13
9
 
14
- /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
15
- export interface DiscordAdapterConfig {
16
- readonly id?: string;
17
- readonly token?: string;
10
+ export interface DiscordActivity {
11
+ readonly name: string;
12
+ readonly type: 'PLAYING' | 'STREAMING' | 'LISTENING' | 'WATCHING' | 'COMPETING';
13
+ readonly url?: string;
14
+ }
15
+
16
+ /** One endpoint config after AdapterIndex expands `plugins.<instanceKey>.endpoints`. */
17
+ export interface DiscordEndpointConfig {
18
+ readonly id: string;
19
+ readonly token: string;
18
20
  /** Default `gateway`. `interactions` uses httpHostToken POST + Ed25519 verify. */
19
21
  readonly connection?: 'gateway' | 'interactions';
20
22
  readonly intents?: readonly number[];
21
23
  readonly enableSlashCommands?: boolean;
22
24
  readonly globalCommands?: boolean;
23
- readonly defaultActivity?: {
24
- readonly name: string;
25
- readonly type: 'PLAYING' | 'STREAMING' | 'LISTENING' | 'WATCHING' | 'COMPETING';
26
- readonly url?: string;
27
- };
25
+ readonly defaultActivity?: DiscordActivity;
28
26
  readonly slashCommands?: readonly Record<string, unknown>[];
29
27
  /** Interactions-only fields. */
30
28
  readonly applicationId?: string;
31
29
  readonly publicKey?: string;
32
30
  readonly interactionsPath?: string;
33
- /** Transitional: legacy root `endpoints[]` with `context: discord`. */
34
- readonly endpoints?: ReadonlyArray<{
35
- readonly context?: string;
36
- readonly id?: string;
37
- readonly token?: string;
38
- readonly connection?: 'gateway' | 'interactions';
39
- readonly intents?: readonly number[];
40
- readonly enableSlashCommands?: boolean;
41
- readonly globalCommands?: boolean;
42
- readonly defaultActivity?: DiscordAdapterConfig['defaultActivity'];
43
- readonly slashCommands?: readonly Record<string, unknown>[];
44
- readonly applicationId?: string;
45
- readonly publicKey?: string;
46
- readonly interactionsPath?: string;
47
- }>;
48
31
  }
49
32
 
50
33
  export interface ResolvedDiscordGatewayConfig {
@@ -55,7 +38,7 @@ export interface ResolvedDiscordGatewayConfig {
55
38
  readonly intents?: readonly number[];
56
39
  readonly enableSlashCommands: boolean;
57
40
  readonly globalCommands: boolean;
58
- readonly defaultActivity?: DiscordAdapterConfig['defaultActivity'];
41
+ readonly defaultActivity?: DiscordActivity;
59
42
  readonly slashCommands?: readonly Record<string, unknown>[];
60
43
  }
61
44
 
@@ -141,33 +124,14 @@ export interface DiscordOutboundBody {
141
124
  readonly components?: ReadonlyArray<DiscordOutboundActionRow>;
142
125
  }
143
126
 
144
- export function resolveDiscordConfig(config: DiscordAdapterConfig = {}): ResolvedDiscordConfig {
145
- const entry = config.endpoints?.find((item) => item.context === 'discord' || !item.context);
146
- const token = (typeof config.token === 'string' && config.token)
147
- || (typeof entry?.token === 'string' && entry.token)
148
- || process.env.DISCORD_BOT_TOKEN
149
- || '';
150
- if (!token) {
151
- throw new TypeError(
152
- 'Discord adapter requires token (plugins.<key>.token or endpoints with context: discord)',
153
- );
154
- }
155
- const id = (typeof config.id === 'string' && config.id)
156
- || (typeof entry?.id === 'string' && entry.id)
157
- || process.env.DISCORD_BOT_NAME
158
- || 'discord-bot';
159
- const connection = config.connection
160
- ?? entry?.connection
161
- ?? 'gateway';
127
+ export function resolveDiscordConfig(config: DiscordEndpointConfig): ResolvedDiscordConfig {
128
+ const id = requiredEndpointField(config.id, 'id');
129
+ const token = requiredEndpointField(config.token, 'token');
130
+ const connection = config.connection ?? 'gateway';
162
131
 
163
132
  if (connection === 'interactions') {
164
- const applicationId = config.applicationId || entry?.applicationId || '';
165
- const publicKey = config.publicKey || entry?.publicKey || '';
166
- if (!applicationId || !publicKey) {
167
- throw new TypeError(
168
- 'Discord connection:interactions requires applicationId and publicKey',
169
- );
170
- }
133
+ const applicationId = requiredEndpointField(config.applicationId, 'applicationId');
134
+ const publicKey = requiredEndpointField(config.publicKey, 'publicKey');
171
135
  return {
172
136
  context: 'discord',
173
137
  connection: 'interactions',
@@ -175,7 +139,7 @@ export function resolveDiscordConfig(config: DiscordAdapterConfig = {}): Resolve
175
139
  token,
176
140
  applicationId,
177
141
  publicKey,
178
- interactionsPath: config.interactionsPath || entry?.interactionsPath || '/discord/interactions',
142
+ interactionsPath: config.interactionsPath || '/discord/interactions',
179
143
  };
180
144
  }
181
145
 
@@ -184,15 +148,24 @@ export function resolveDiscordConfig(config: DiscordAdapterConfig = {}): Resolve
184
148
  connection: 'gateway',
185
149
  id,
186
150
  token,
187
- intents: config.intents ?? entry?.intents,
188
- enableSlashCommands: config.enableSlashCommands === true
189
- || entry?.enableSlashCommands === true,
190
- globalCommands: config.globalCommands === true || entry?.globalCommands === true,
191
- defaultActivity: config.defaultActivity ?? entry?.defaultActivity,
192
- slashCommands: config.slashCommands ?? entry?.slashCommands,
151
+ intents: config.intents,
152
+ enableSlashCommands: config.enableSlashCommands === true,
153
+ globalCommands: config.globalCommands === true,
154
+ defaultActivity: config.defaultActivity,
155
+ slashCommands: config.slashCommands,
193
156
  };
194
157
  }
195
158
 
159
+ function requiredEndpointField(
160
+ value: unknown,
161
+ field: 'id' | 'token' | 'applicationId' | 'publicKey',
162
+ ): string {
163
+ if (typeof value !== 'string' || !value.trim()) {
164
+ throw new TypeError(`Discord endpoint requires a non-empty ${field}`);
165
+ }
166
+ return value.trim();
167
+ }
168
+
196
169
  export function resolveChannelKind(channelType: number | string | undefined): 'private' | 'group' | 'channel' {
197
170
  // discord.js ChannelType.DM = 1, GroupDM = 3
198
171
  if (channelType === 1 || channelType === 'DM' || channelType === 'private') return 'private';
@@ -431,7 +404,7 @@ export function formatOutboundBody(payload: unknown): DiscordOutboundBody {
431
404
  }
432
405
 
433
406
  export function activityTypeCode(
434
- type: NonNullable<DiscordAdapterConfig['defaultActivity']>['type'],
407
+ type: DiscordActivity['type'],
435
408
  ): number {
436
409
  const map = {
437
410
  PLAYING: 0,