@zhin.js/adapter-github 1.0.0 → 1.1.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.
- package/CHANGELOG.md +1118 -0
- package/README.md +70 -191
- package/adapters/github.js +51 -0
- package/adapters/github.ts +59 -0
- package/agent/prompt-sections/platform.ts +16 -0
- package/{skills/github/SKILL.md → agent/skills/github.md} +26 -9
- package/agent/tools/bind.ts +13 -0
- package/agent/tools/create_pr.ts +20 -0
- package/agent/tools/install.ts +13 -0
- package/agent/tools/patch_file.ts +19 -0
- package/agent/tools/prepare_workspace.ts +15 -0
- package/agent/tools/push_branch.ts +18 -0
- package/agent/tools/star.ts +16 -0
- package/agent/tools/subscribe.ts +16 -0
- package/agent/tools/subscriptions.ts +13 -0
- package/agent/tools/unbind.ts +13 -0
- package/agent/tools/unsubscribe.ts +15 -0
- package/agent/tools/whoami.ts +13 -0
- package/commands/endpoint/add/[id].js +3 -0
- package/commands/endpoint/add/[id].ts +3 -0
- package/commands/endpoint/list.js +3 -0
- package/commands/endpoint/list.ts +3 -0
- package/commands/endpoint/remove/[id].js +3 -0
- package/commands/endpoint/remove/[id].ts +3 -0
- package/lib/client.d.ts +36 -0
- package/lib/client.js +47 -0
- package/lib/endpoint.d.ts +36 -0
- package/lib/endpoint.js +140 -0
- package/lib/gh-client.d.ts +73 -1
- package/lib/gh-client.js +109 -7
- package/lib/github-bot-handlers.d.ts +27 -0
- package/lib/github-bot-handlers.js +76 -0
- package/lib/github-channel-context.d.ts +16 -0
- package/lib/github-channel-context.js +31 -0
- package/lib/github-endpoint-commands.d.ts +1 -0
- package/lib/github-endpoint-commands.js +22 -0
- package/lib/github-runtime-state.d.ts +1 -0
- package/lib/github-runtime-state.js +6 -0
- package/lib/github-tool-handlers.d.ts +18 -0
- package/lib/github-tool-handlers.js +214 -0
- package/lib/index.d.ts +7 -32
- package/lib/index.js +7 -385
- package/lib/oauth-users.d.ts +33 -0
- package/lib/oauth-users.js +38 -0
- package/lib/protocol.d.ts +94 -0
- package/lib/protocol.js +292 -0
- package/lib/types.d.ts +9 -4
- package/lib/types.js +1 -2
- package/lib/webhook.d.ts +14 -0
- package/lib/webhook.js +88 -0
- package/lib/workspace-manager.d.ts +21 -0
- package/lib/workspace-manager.js +154 -0
- package/package.json +77 -22
- package/plugin.js +38 -0
- package/schema.json +130 -0
- package/src/client.ts +65 -0
- package/src/endpoint.ts +169 -0
- package/src/gh-client.ts +147 -12
- package/src/github-bot-handlers.ts +113 -0
- package/src/github-channel-context.ts +46 -0
- package/src/github-endpoint-commands.ts +23 -0
- package/src/github-runtime-state.ts +7 -0
- package/src/github-tool-handlers.ts +252 -0
- package/src/index.ts +48 -431
- package/src/oauth-users.ts +47 -0
- package/src/protocol.ts +425 -0
- package/src/types.ts +10 -4
- package/src/webhook.ts +130 -0
- package/src/workspace-manager.ts +168 -0
- package/lib/adapter.d.ts +0 -63
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -415
- package/lib/adapter.js.map +0 -1
- package/lib/agent-prompt.d.ts +0 -3
- package/lib/agent-prompt.d.ts.map +0 -1
- package/lib/agent-prompt.js +0 -82
- package/lib/agent-prompt.js.map +0 -1
- package/lib/bot.d.ts +0 -26
- package/lib/bot.d.ts.map +0 -1
- package/lib/bot.js +0 -160
- package/lib/bot.js.map +0 -1
- package/lib/gh-client.d.ts.map +0 -1
- package/lib/gh-client.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/register-github-mcp.d.ts +0 -6
- package/lib/register-github-mcp.d.ts.map +0 -1
- package/lib/register-github-mcp.js +0 -35
- package/lib/register-github-mcp.js.map +0 -1
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -446
- package/src/agent-prompt.ts +0 -99
- package/src/bot.ts +0 -174
- package/src/register-github-mcp.ts +0 -60
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-github",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Zhin.js adapter for
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Zhin.js GitHub adapter for Plugin Runtime (App auth + webhook)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"types": "./lib/index.d.ts",
|
|
@@ -18,15 +18,6 @@
|
|
|
18
18
|
},
|
|
19
19
|
"./package.json": "./package.json"
|
|
20
20
|
},
|
|
21
|
-
"files": [
|
|
22
|
-
"src",
|
|
23
|
-
"lib",
|
|
24
|
-
"client",
|
|
25
|
-
"dist",
|
|
26
|
-
"skills",
|
|
27
|
-
"plugin.yml",
|
|
28
|
-
"README.md"
|
|
29
|
-
],
|
|
30
21
|
"keywords": [
|
|
31
22
|
"zhin",
|
|
32
23
|
"zhin.js",
|
|
@@ -44,30 +35,94 @@
|
|
|
44
35
|
"url": "https://github.com/lc-cn"
|
|
45
36
|
},
|
|
46
37
|
"license": "MIT",
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/zhinjs/zhin.git",
|
|
41
|
+
"directory": "plugins/adapters/github"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@zhin.js/adapter": "1.1.12",
|
|
45
|
+
"@zhin.js/core": "1.1.35",
|
|
46
|
+
"@zhin.js/feature-kit": "1.1.0",
|
|
47
|
+
"@zhin.js/host-http": "1.1.0",
|
|
48
|
+
"@zhin.js/im-contract": "1.1.0",
|
|
49
|
+
"@zhin.js/logger": "1.1.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"
|
|
53
|
-
"zhin.js": "
|
|
52
|
+
"zod": "^4.0.0",
|
|
53
|
+
"@zhin.js/adapter": "^1.1.12",
|
|
54
|
+
"@zhin.js/agent": "^1.1.23",
|
|
55
|
+
"@zhin.js/command": "^1.1.0",
|
|
56
|
+
"@zhin.js/core": "^1.1.35",
|
|
57
|
+
"@zhin.js/host-http": "^1.1.0",
|
|
58
|
+
"@zhin.js/prompt-section": "^1.1.0",
|
|
59
|
+
"zhin.js": "^1.1.0"
|
|
54
60
|
},
|
|
55
61
|
"peerDependenciesMeta": {
|
|
56
|
-
"@zhin.js/
|
|
62
|
+
"@zhin.js/agent": {
|
|
63
|
+
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"@zhin.js/command": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"@zhin.js/prompt-section": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"zhin.js": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"zod": {
|
|
57
75
|
"optional": true
|
|
58
76
|
}
|
|
59
77
|
},
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
78
|
+
"devDependencies": {
|
|
79
|
+
"@types/node": "^26.1.2",
|
|
80
|
+
"typescript": "^6.0.3",
|
|
81
|
+
"vitest": "^4.1.10",
|
|
82
|
+
"zod": "^4.4.3",
|
|
83
|
+
"@zhin.js/agent": "1.1.23",
|
|
84
|
+
"@zhin.js/prompt-section": "1.1.0",
|
|
85
|
+
"zhin.js": "1.1.0"
|
|
64
86
|
},
|
|
87
|
+
"files": [
|
|
88
|
+
"adapters",
|
|
89
|
+
"commands",
|
|
90
|
+
"plugin.js",
|
|
91
|
+
"schema.json",
|
|
92
|
+
"src",
|
|
93
|
+
"lib",
|
|
94
|
+
"agent",
|
|
95
|
+
"README.md",
|
|
96
|
+
"CHANGELOG.md"
|
|
97
|
+
],
|
|
65
98
|
"publishConfig": {
|
|
66
99
|
"access": "public",
|
|
67
100
|
"registry": "https://registry.npmjs.org"
|
|
68
101
|
},
|
|
102
|
+
"engines": {
|
|
103
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
104
|
+
},
|
|
105
|
+
"zhin": {
|
|
106
|
+
"protocol": 1,
|
|
107
|
+
"type": "plugin",
|
|
108
|
+
"entry": "./plugin.js",
|
|
109
|
+
"engine": "^1.0.0",
|
|
110
|
+
"runtime": "trusted",
|
|
111
|
+
"features": [
|
|
112
|
+
{
|
|
113
|
+
"package": "@zhin.js/adapter",
|
|
114
|
+
"api": "^1.0.0"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"package": "@zhin.js/command",
|
|
118
|
+
"api": "^1.0.0"
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"plugins": []
|
|
122
|
+
},
|
|
69
123
|
"scripts": {
|
|
70
124
|
"build": "tsc",
|
|
71
|
-
"clean": "rimraf lib"
|
|
125
|
+
"clean": "rimraf lib",
|
|
126
|
+
"test": "NODE_OPTIONS=--experimental-strip-types vitest run --root ../../.. plugins/adapters/github/tests"
|
|
72
127
|
}
|
|
73
128
|
}
|
package/plugin.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { createEndpointRuntimeState } from 'zhin.js/adapter';
|
|
3
|
+
import { definePlugin, databaseHostToken } from 'zhin.js';
|
|
4
|
+
import { githubRuntimeStateToken } from "./lib/github-runtime-state.js";
|
|
5
|
+
import { defineGithubOauthUsersTable } from "./lib/oauth-users.js";
|
|
6
|
+
/**
|
|
7
|
+
* github_subscriptions — repo event subscriptions per chat channel
|
|
8
|
+
* (used by github_subscriptions agent tool; schema matches legacy defineModel).
|
|
9
|
+
*/
|
|
10
|
+
const GITHUB_SUBSCRIPTIONS_SCHEMA = {
|
|
11
|
+
id: { type: 'integer', primary: true },
|
|
12
|
+
repo: { type: 'text', nullable: false },
|
|
13
|
+
events: { type: 'json', default: [] },
|
|
14
|
+
target_id: { type: 'text', nullable: false },
|
|
15
|
+
target_type: { type: 'text', nullable: false },
|
|
16
|
+
adapter: { type: 'text', nullable: false },
|
|
17
|
+
endpoint: { type: 'text', nullable: false },
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Plugin Runtime GitHub adapter.
|
|
21
|
+
* - Endpoint: `adapters/github.ts`
|
|
22
|
+
* - OAuth user tokens: define `github_oauth_users` when DatabaseHost is present
|
|
23
|
+
*/
|
|
24
|
+
export default definePlugin({
|
|
25
|
+
name: 'github',
|
|
26
|
+
metadata: {
|
|
27
|
+
displayName: 'GitHub Adapter',
|
|
28
|
+
},
|
|
29
|
+
setup(context) {
|
|
30
|
+
// 运行中 endpoint 注册表(github.endpoint list 的"运行中"数据源)
|
|
31
|
+
context.resources.provide(githubRuntimeStateToken, createEndpointRuntimeState());
|
|
32
|
+
if (context.resources.has(databaseHostToken)) {
|
|
33
|
+
const host = context.resources.use(databaseHostToken);
|
|
34
|
+
defineGithubOauthUsersTable(host);
|
|
35
|
+
host.define('github_subscriptions', { ...GITHUB_SUBSCRIPTIONS_SCHEMA });
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
});
|
package/schema.json
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"additionalProperties": false,
|
|
5
|
+
"properties": {
|
|
6
|
+
"host": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"description": "GitHub Enterprise hostname (default github.com)"
|
|
9
|
+
},
|
|
10
|
+
"webhook_path": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"default": "/github/webhook"
|
|
13
|
+
},
|
|
14
|
+
"webhookPath": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"default": "/github/webhook"
|
|
17
|
+
},
|
|
18
|
+
"poll_interval": {
|
|
19
|
+
"type": "number",
|
|
20
|
+
"default": 60,
|
|
21
|
+
"description": "Deferred: polling fallback was removed in the Plugin Runtime migration; currently parsed but unused"
|
|
22
|
+
},
|
|
23
|
+
"auto_reply_repos": {
|
|
24
|
+
"type": "array",
|
|
25
|
+
"items": {
|
|
26
|
+
"type": "string"
|
|
27
|
+
},
|
|
28
|
+
"description": "Repos whose Issue/PR comments auto-trigger AI without @bot"
|
|
29
|
+
},
|
|
30
|
+
"bot_login": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Override App bot login (default {slug}[bot])"
|
|
33
|
+
},
|
|
34
|
+
"workspace_root": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Managed git workspace root"
|
|
37
|
+
},
|
|
38
|
+
"master": {
|
|
39
|
+
"type": [
|
|
40
|
+
"string",
|
|
41
|
+
"number"
|
|
42
|
+
],
|
|
43
|
+
"description": "框架 master(GitHub user login or id;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
|
|
44
|
+
},
|
|
45
|
+
"trusted": {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"items": {
|
|
48
|
+
"type": [
|
|
49
|
+
"string",
|
|
50
|
+
"number"
|
|
51
|
+
],
|
|
52
|
+
"description": "Trusted GitHub user login or id"
|
|
53
|
+
},
|
|
54
|
+
"description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
|
|
55
|
+
},
|
|
56
|
+
"endpoints": {
|
|
57
|
+
"type": "array",
|
|
58
|
+
"description": "多账号:一个插件实例挂多个 endpoint",
|
|
59
|
+
"items": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"additionalProperties": true,
|
|
62
|
+
"properties": {
|
|
63
|
+
"master": {
|
|
64
|
+
"type": [
|
|
65
|
+
"string",
|
|
66
|
+
"number"
|
|
67
|
+
],
|
|
68
|
+
"description": "本 endpoint 的框架 master(GitHub user login or id);覆盖顶层 master"
|
|
69
|
+
},
|
|
70
|
+
"trusted": {
|
|
71
|
+
"type": "array",
|
|
72
|
+
"items": {
|
|
73
|
+
"type": [
|
|
74
|
+
"string",
|
|
75
|
+
"number"
|
|
76
|
+
],
|
|
77
|
+
"description": "Trusted GitHub user login or id"
|
|
78
|
+
},
|
|
79
|
+
"description": "本 endpoint 的 trusted 列表"
|
|
80
|
+
},
|
|
81
|
+
"app_id": {
|
|
82
|
+
"type": [
|
|
83
|
+
"string",
|
|
84
|
+
"number"
|
|
85
|
+
],
|
|
86
|
+
"description": "GitHub App ID"
|
|
87
|
+
},
|
|
88
|
+
"appId": {
|
|
89
|
+
"type": [
|
|
90
|
+
"string",
|
|
91
|
+
"number"
|
|
92
|
+
],
|
|
93
|
+
"description": "GitHub App ID (camelCase alias)"
|
|
94
|
+
},
|
|
95
|
+
"private_key": {
|
|
96
|
+
"type": "string",
|
|
97
|
+
"description": "GitHub App private key (PEM content or file path)"
|
|
98
|
+
},
|
|
99
|
+
"privateKey": {
|
|
100
|
+
"type": "string",
|
|
101
|
+
"description": "GitHub App private key (camelCase alias)"
|
|
102
|
+
},
|
|
103
|
+
"webhook_secret": {
|
|
104
|
+
"type": "string",
|
|
105
|
+
"description": "Webhook HMAC secret; enables httpHostToken POST route"
|
|
106
|
+
},
|
|
107
|
+
"webhookSecret": {
|
|
108
|
+
"type": "string",
|
|
109
|
+
"description": "Webhook HMAC secret (camelCase alias)"
|
|
110
|
+
},
|
|
111
|
+
"id": {
|
|
112
|
+
"type": "string",
|
|
113
|
+
"description": "GitHub App bot name"
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"required": [
|
|
117
|
+
"id"
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
"commandPrefix": {
|
|
122
|
+
"type": "string",
|
|
123
|
+
"default": "",
|
|
124
|
+
"description": "命令前缀(默认 '' 无前缀,任意文本按命令匹配;如 '/' 要求 / 开头)。endpoints[i] 可逐项覆盖"
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"required": [
|
|
128
|
+
"endpoints"
|
|
129
|
+
]
|
|
130
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import type { PluginDatabaseHost } from 'zhin.js';
|
|
3
|
+
import { defineEndpointClient } from 'zhin.js/adapter';
|
|
4
|
+
import { GhClient } from './gh-client.js';
|
|
5
|
+
import { lookupGithubOauthAccessToken } from './oauth-users.js';
|
|
6
|
+
import type { ResolvedGithubConfig } from './protocol.js';
|
|
7
|
+
import { WorkspaceManager } from './workspace-manager.js';
|
|
8
|
+
|
|
9
|
+
/** GitHub SDK surface exposed to plugin handlers and Agent tools. */
|
|
10
|
+
export class GithubClient {
|
|
11
|
+
#workspaceManager?: WorkspaceManager;
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
readonly name: string,
|
|
15
|
+
readonly api: GhClient,
|
|
16
|
+
readonly config: ResolvedGithubConfig,
|
|
17
|
+
readonly database?: PluginDatabaseHost,
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
async getUserOrDefaultApi(platform?: string, platformUid?: string): Promise<GhClient> {
|
|
21
|
+
if (platform && platformUid) {
|
|
22
|
+
const token = await lookupGithubOauthAccessToken(this.database, platform, platformUid);
|
|
23
|
+
if (token) return this.api.withToken(token);
|
|
24
|
+
}
|
|
25
|
+
return this.api;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get clientId(): string | null {
|
|
29
|
+
return this.api.clientId || null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get host(): string | undefined {
|
|
33
|
+
return this.config.host;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get appSlug(): string | null {
|
|
37
|
+
return this.api.appSlug || null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get installations() {
|
|
41
|
+
return this.api.installations || [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get workspaceManager(): WorkspaceManager {
|
|
45
|
+
if (!this.#workspaceManager) {
|
|
46
|
+
const root = this.config.workspaceRoot
|
|
47
|
+
?? path.join(process.cwd(), 'data', 'github-workspaces');
|
|
48
|
+
this.#workspaceManager = new WorkspaceManager(this.api, root);
|
|
49
|
+
}
|
|
50
|
+
return this.#workspaceManager;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type GithubClientEventMap = Record<string, unknown>;
|
|
55
|
+
|
|
56
|
+
declare module '@zhin.js/feature-kit' {
|
|
57
|
+
interface AdapterClientRegistry {
|
|
58
|
+
readonly github: {
|
|
59
|
+
readonly client: GithubClient;
|
|
60
|
+
readonly events: GithubClientEventMap;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const githubClient = defineEndpointClient<GithubClient, GithubClientEventMap>('github');
|
package/src/endpoint.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Endpoint } from 'zhin.js/adapter';
|
|
2
|
+
/**
|
|
3
|
+
* GithubEndpoint — lifecycle, outbound send, inbound admit.
|
|
4
|
+
*/
|
|
5
|
+
import { type EndpointSendRequest } from 'zhin.js/adapter';
|
|
6
|
+
import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
7
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
8
|
+
import type { CapabilityId, PluginDatabaseHost } from 'zhin.js';
|
|
9
|
+
import { GhClient } from './gh-client.js';
|
|
10
|
+
import {
|
|
11
|
+
enrichInboundContent,
|
|
12
|
+
formatInboundContent,
|
|
13
|
+
formatOutboundBody,
|
|
14
|
+
githubInboundConversation,
|
|
15
|
+
parseChannelId,
|
|
16
|
+
type GithubInboundComment,
|
|
17
|
+
type ResolvedGithubConfig,
|
|
18
|
+
} from './protocol.js';
|
|
19
|
+
import { registerGithubWebhookRoutes } from './webhook.js';
|
|
20
|
+
import { GithubClient } from './client.js';
|
|
21
|
+
|
|
22
|
+
export interface GithubEndpointOptions {
|
|
23
|
+
readonly id: CapabilityId;
|
|
24
|
+
readonly http?: HttpHost;
|
|
25
|
+
readonly database?: PluginDatabaseHost;
|
|
26
|
+
readonly config: ResolvedGithubConfig;
|
|
27
|
+
readonly createClient?: (config: ResolvedGithubConfig) => GhClient;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* GitHub 是代码协作面(issue/PR),无好友/群/频道等 IM 社交概念,
|
|
32
|
+
* 不适用 EndpointManagement 语义端口;本 endpoint 不暴露该端口。
|
|
33
|
+
*/
|
|
34
|
+
export class GithubEndpoint extends Endpoint<GithubClient> {
|
|
35
|
+
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
36
|
+
|
|
37
|
+
readonly #options: GithubEndpointOptions;
|
|
38
|
+
readonly client: GithubClient;
|
|
39
|
+
#routeReleases: HttpRouteRegistration[] = [];
|
|
40
|
+
#open = false;
|
|
41
|
+
#started = false;
|
|
42
|
+
|
|
43
|
+
constructor(options: GithubEndpointOptions) {
|
|
44
|
+
super();
|
|
45
|
+
this.#logger = getAdapterLogger('github', options.config.id);
|
|
46
|
+
this.#options = options;
|
|
47
|
+
const api = options.createClient?.(options.config) ?? defaultCreateClient(options.config);
|
|
48
|
+
this.client = new GithubClient(options.config.id, api, options.config, options.database);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get config(): ResolvedGithubConfig {
|
|
52
|
+
return this.client.config;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async start(): Promise<void> {
|
|
56
|
+
if (this.#started) return;
|
|
57
|
+
this.#started = true;
|
|
58
|
+
try {
|
|
59
|
+
const result = await this.client.api.verifyAuth();
|
|
60
|
+
if (!result.ok) throw new Error(`GitHub 认证失败: ${result.message}`);
|
|
61
|
+
if (this.client.config.webhookSecret) {
|
|
62
|
+
if (!this.#options.http) {
|
|
63
|
+
throw new TypeError('GitHub webhook_secret requires httpHostToken');
|
|
64
|
+
}
|
|
65
|
+
this.#routeReleases.push(...registerGithubWebhookRoutes(this.#options.http, this));
|
|
66
|
+
this.#logger.debug(formatCompact({
|
|
67
|
+
endpoint: this.client.name,
|
|
68
|
+
op: 'webhook',
|
|
69
|
+
path: this.client.config.webhookPath,
|
|
70
|
+
}));
|
|
71
|
+
} else {
|
|
72
|
+
this.#logger.debug(formatCompact({
|
|
73
|
+
endpoint: this.client.name,
|
|
74
|
+
op: 'connect',
|
|
75
|
+
mode: 'api-only',
|
|
76
|
+
bot: this.client.api.authenticatedUser,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
await this.stop();
|
|
81
|
+
this.#logger.error('Failed to connect GitHub endpoint:', error);
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
open(): void {
|
|
87
|
+
this.#open = true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
close(): void {
|
|
91
|
+
this.#open = false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async stop(): Promise<void> {
|
|
95
|
+
this.#open = false;
|
|
96
|
+
for (const release of this.#routeReleases.splice(0)) release();
|
|
97
|
+
this.#started = false;
|
|
98
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
102
|
+
const parsed = parseChannelId(conversation.id);
|
|
103
|
+
if (!parsed) throw new Error(`无效的 GitHub conversation ID: ${conversation.id}`);
|
|
104
|
+
const text = formatOutboundBody(payload);
|
|
105
|
+
const r = parsed.type === 'issue'
|
|
106
|
+
? await this.client.api.createIssueComment(parsed.repo, parsed.number, text)
|
|
107
|
+
: await this.client.api.createPRComment(parsed.repo, parsed.number, text);
|
|
108
|
+
if (!r.ok) throw new Error(`发送失败: ${JSON.stringify(r.data)}`);
|
|
109
|
+
this.#logger.debug(formatCompact({
|
|
110
|
+
op: 'github_send',
|
|
111
|
+
endpoint: this.client.name,
|
|
112
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
113
|
+
messageId: r.data.id,
|
|
114
|
+
}));
|
|
115
|
+
return String(r.data.id);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Test / internal: admit a parsed comment when open. */
|
|
119
|
+
admit(comment: GithubInboundComment): void {
|
|
120
|
+
if (!this.#open) return;
|
|
121
|
+
const botUser = this.client.config.botLogin
|
|
122
|
+
|| this.client.api.getBotLogin()
|
|
123
|
+
|| this.client.api.authenticatedUser;
|
|
124
|
+
if (botUser && comment.sender === botUser) return;
|
|
125
|
+
const conversation = githubInboundConversation(String(this.#options.id), comment);
|
|
126
|
+
const content = enrichInboundContent(
|
|
127
|
+
formatInboundContent(comment.content),
|
|
128
|
+
this.client.config,
|
|
129
|
+
botUser ?? undefined,
|
|
130
|
+
comment.repo,
|
|
131
|
+
);
|
|
132
|
+
void this.emit('message.receive', {
|
|
133
|
+
conversation,
|
|
134
|
+
message: { conversation, id: comment.id },
|
|
135
|
+
content,
|
|
136
|
+
sender: { id: comment.sender, name: comment.sender },
|
|
137
|
+
endpointId: this.client.name,
|
|
138
|
+
metadata: Object.freeze({
|
|
139
|
+
repo: comment.repo,
|
|
140
|
+
kind: comment.kind,
|
|
141
|
+
createdAt: comment.createdAt,
|
|
142
|
+
}),
|
|
143
|
+
}).catch((err) => {
|
|
144
|
+
this.#logger.warn(formatCompact({
|
|
145
|
+
op: 'github_gateway_receive_failed',
|
|
146
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
147
|
+
error: err instanceof Error ? err.message : String(err),
|
|
148
|
+
}));
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
admitPlatform(name: string, event: unknown): void {
|
|
153
|
+
if (!this.#open) return;
|
|
154
|
+
void this.emitPlatform(name, event).catch((error) => {
|
|
155
|
+
this.#logger.warn(formatCompact({
|
|
156
|
+
op: 'github_platform_event_failed',
|
|
157
|
+
event: name,
|
|
158
|
+
error: error instanceof Error ? error.message : String(error),
|
|
159
|
+
}));
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function defaultCreateClient(config: ResolvedGithubConfig): GhClient {
|
|
165
|
+
const appAuth = config.appId && config.privateKey
|
|
166
|
+
? { appId: config.appId, privateKey: config.privateKey }
|
|
167
|
+
: undefined;
|
|
168
|
+
return new GhClient({ host: config.host, appAuth });
|
|
169
|
+
}
|