agent-bridge-chatgpt 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -2
- package/config.schema.json +1 -0
- package/dist/src-ts/agent.js +10 -0
- package/dist/src-ts/plugins/chatgpt/cdp-client.js +304 -0
- package/dist/src-ts/plugins/chatgpt/index.js +90 -0
- package/package.json +1 -6
- package/plugin.json +1 -0
package/README.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ChatGPT(Codex)插件
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
插件通过 SSH 本地转发和 CDP,向远程 Windows 上已登录的 ChatGPT 桌面端发送消息并读取回复。插件不会保存 ChatGPT 账号或密码。
|
|
4
|
+
|
|
5
|
+
## 远程桌面准备
|
|
6
|
+
|
|
7
|
+
在远程 Windows 的已登录桌面会话中,先正常退出 ChatGPT,再打开 PowerShell 执行以下命令:
|
|
8
|
+
|
|
9
|
+
```powershell
|
|
10
|
+
$package = Get-AppxPackage -Name OpenAI.CodexBeta
|
|
11
|
+
$executable = Join-Path $package.InstallLocation 'app\ChatGPT (Beta).exe'
|
|
12
|
+
Start-Process -FilePath $executable -ArgumentList '--remote-debugging-port=9230'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
启动后在桌面端确认账号仍处于登录状态。CDP 端口由 SSH 隧道访问,不需要开放到局域网。
|
|
16
|
+
|
|
17
|
+
## 插件配置
|
|
18
|
+
|
|
19
|
+
在 Agent Bridge 的 `chatgpt` 插件配置中填写:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"sshHost": "192.168.31.151",
|
|
24
|
+
"sshUser": "m2lih",
|
|
25
|
+
"sshKeyPath": "/本机/SSH/私钥路径",
|
|
26
|
+
"remoteCdpPort": 9230,
|
|
27
|
+
"localCdpPort": 9230
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
插件启动时会建立 SSH 转发,并连接本机 `127.0.0.1:9230`。不配置 SSH 时,可直接连接本机已开启 CDP 的 ChatGPT 页面。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"object","properties":{"sshHost":{"type":"string","description":"Windows 主机地址"},"sshUser":{"type":"string","description":"SSH 用户名"},"sshPort":{"type":"integer","minimum":1,"maximum":65535,"default":22},"sshKeyPath":{"type":"string","description":"SSH 私钥路径"},"remoteCdpPort":{"type":"integer","minimum":1,"maximum":65535,"default":9230},"localCdpPort":{"type":"integer","minimum":1,"maximum":65535,"default":9230},"timeoutMs":{"type":"integer","minimum":1,"default":30000}},"additionalProperties":false}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AgentError = void 0;
|
|
4
|
+
class AgentError extends Error {
|
|
5
|
+
constructor(message, options) {
|
|
6
|
+
super(message, options);
|
|
7
|
+
this.name = "AgentError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.AgentError = AgentError;
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ChatgptCdpClient = exports.userBubbleSelector = exports.turnSelector = exports.editorSelector = void 0;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
exports.editorSelector = '[data-codex-composer="true"][contenteditable="true"]';
|
|
6
|
+
exports.turnSelector = '[data-chatgpt-conversation-turn="true"]';
|
|
7
|
+
exports.userBubbleSelector = "[data-user-message-bubble]";
|
|
8
|
+
const assistantMessageSelector = '[data-markdown-text-style="assistant-message"]';
|
|
9
|
+
const turnIdAttribute = "data-chatgpt-conversation-turn-id";
|
|
10
|
+
const contentUnitSelector = "[data-content-search-unit-key]";
|
|
11
|
+
const sendButtonSelector = 'button[aria-label="Send"], button[aria-label="发送"]';
|
|
12
|
+
const stopButtonSelector = 'button[aria-label="Stop"], button[aria-label="停止"]';
|
|
13
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
14
|
+
class ChatgptCdpClient {
|
|
15
|
+
socket;
|
|
16
|
+
tunnel;
|
|
17
|
+
sequence = 0;
|
|
18
|
+
pending = new Map();
|
|
19
|
+
port;
|
|
20
|
+
timeoutMs;
|
|
21
|
+
fetchImpl;
|
|
22
|
+
webSocketFactory;
|
|
23
|
+
ssh;
|
|
24
|
+
constructor(options = {}) {
|
|
25
|
+
this.port = options.port ?? options.ssh?.localPort ?? 9230;
|
|
26
|
+
this.timeoutMs = options.timeoutMs ?? 30_000;
|
|
27
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
28
|
+
this.webSocketFactory = options.webSocketFactory ?? ((url) => new WebSocket(url));
|
|
29
|
+
this.ssh = options.ssh;
|
|
30
|
+
}
|
|
31
|
+
connected() { return Boolean(this.socket); }
|
|
32
|
+
async connect() {
|
|
33
|
+
if (this.socket)
|
|
34
|
+
return;
|
|
35
|
+
try {
|
|
36
|
+
if (this.ssh) {
|
|
37
|
+
await this.startTunnel();
|
|
38
|
+
}
|
|
39
|
+
const page = await this.findPage();
|
|
40
|
+
if (!page.webSocketDebuggerUrl)
|
|
41
|
+
throw new Error("ChatGPT CDP target 缺少 WebSocket 地址");
|
|
42
|
+
const socketUrl = new URL(page.webSocketDebuggerUrl);
|
|
43
|
+
if (this.ssh) {
|
|
44
|
+
socketUrl.hostname = "127.0.0.1";
|
|
45
|
+
socketUrl.port = String(this.port);
|
|
46
|
+
}
|
|
47
|
+
const socket = this.webSocketFactory(socketUrl);
|
|
48
|
+
this.socket = socket;
|
|
49
|
+
let opened = false;
|
|
50
|
+
let rejectOpening;
|
|
51
|
+
socket.onmessage = (event) => this.onMessage(String(event.data));
|
|
52
|
+
socket.onclose = () => {
|
|
53
|
+
if (this.socket === socket) {
|
|
54
|
+
this.socket = undefined;
|
|
55
|
+
this.tunnel?.kill();
|
|
56
|
+
this.tunnel = undefined;
|
|
57
|
+
}
|
|
58
|
+
if (!opened)
|
|
59
|
+
rejectOpening?.(new Error("ChatGPT CDP WebSocket 连接已关闭"));
|
|
60
|
+
this.rejectPending(new Error("ChatGPT CDP 连接已关闭"));
|
|
61
|
+
};
|
|
62
|
+
await new Promise((resolve, reject) => {
|
|
63
|
+
rejectOpening = reject;
|
|
64
|
+
const timer = setTimeout(() => reject(new Error("ChatGPT CDP WebSocket 连接超时")), this.timeoutMs);
|
|
65
|
+
socket.onopen = () => {
|
|
66
|
+
opened = true;
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
resolve();
|
|
69
|
+
};
|
|
70
|
+
socket.onerror = () => {
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
reject(new Error("ChatGPT CDP WebSocket 连接失败"));
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
this.close();
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async findPage() {
|
|
82
|
+
const deadline = Date.now() + this.timeoutMs;
|
|
83
|
+
let last;
|
|
84
|
+
while (Date.now() < deadline) {
|
|
85
|
+
try {
|
|
86
|
+
const response = await this.fetchImpl(`http://127.0.0.1:${this.port}/json`, {
|
|
87
|
+
signal: AbortSignal.timeout(Math.min(this.timeoutMs, 3_000)),
|
|
88
|
+
});
|
|
89
|
+
if (!response.ok)
|
|
90
|
+
throw new Error(`ChatGPT CDP /json HTTP ${response.status}`);
|
|
91
|
+
const targets = await response.json();
|
|
92
|
+
const page = targets.find((target) => target.type === "page" && (/chatgpt|codex/i.test(`${target.title} ${target.url}`)
|
|
93
|
+
|| target.url === "app://-/index.html"));
|
|
94
|
+
if (page?.webSocketDebuggerUrl)
|
|
95
|
+
return page;
|
|
96
|
+
last = new Error(`未找到 ChatGPT/Codex 页面 target(端口 ${this.port})`);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
last = error;
|
|
100
|
+
}
|
|
101
|
+
await sleep(500);
|
|
102
|
+
}
|
|
103
|
+
throw new Error(`ChatGPT CDP 连接超时:${last instanceof Error ? last.message : String(last ?? "端口不可达")}`, { cause: last });
|
|
104
|
+
}
|
|
105
|
+
close() {
|
|
106
|
+
this.socket?.close();
|
|
107
|
+
this.socket = undefined;
|
|
108
|
+
this.tunnel?.kill();
|
|
109
|
+
this.tunnel = undefined;
|
|
110
|
+
this.rejectPending(new Error("ChatGPT CDP 已关闭"));
|
|
111
|
+
}
|
|
112
|
+
async direct(text, timeoutMs = 240_000) {
|
|
113
|
+
await this.waitFor(`Boolean(document.querySelector(${JSON.stringify(exports.editorSelector)}))`, "ChatGPT 桌面端输入框未就绪");
|
|
114
|
+
const beforeTurnsJson = await this.evaluate(`JSON.stringify([...document.querySelectorAll(${JSON.stringify(exports.turnSelector)})].map(turn => ({ id: turn.getAttribute(${JSON.stringify(turnIdAttribute)}), users: turn.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)}).length })))`);
|
|
115
|
+
const beforeTurns = JSON.parse(String(beforeTurnsJson || "[]"));
|
|
116
|
+
const beforeAssistantCount = Number(await this.evaluate(`document.querySelectorAll(${JSON.stringify(assistantMessageSelector)}).length`));
|
|
117
|
+
const ready = await this.evaluate(`(() => {
|
|
118
|
+
const editor = document.querySelector(${JSON.stringify(exports.editorSelector)});
|
|
119
|
+
if (!editor || editor.innerText.trim()) return false;
|
|
120
|
+
const findButton = () => {
|
|
121
|
+
for (let parent = editor; parent && parent !== document.body; parent = parent.parentElement) {
|
|
122
|
+
const buttons = [...parent.querySelectorAll(${JSON.stringify(sendButtonSelector)})];
|
|
123
|
+
if (buttons.length === 1) return buttons[0];
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
};
|
|
127
|
+
const button = findButton();
|
|
128
|
+
if (!button) return false;
|
|
129
|
+
editor.focus();
|
|
130
|
+
return document.activeElement === editor;
|
|
131
|
+
})()`);
|
|
132
|
+
if (!ready)
|
|
133
|
+
throw new Error("ChatGPT 输入框含有未发送内容,或桌面端发送按钮未就绪");
|
|
134
|
+
await this.cdp("Input.insertText", { text });
|
|
135
|
+
const inputMatches = await this.evaluate(`document.querySelector(${JSON.stringify(exports.editorSelector)})?.innerText.trim() === ${JSON.stringify(text.trim())}`);
|
|
136
|
+
if (!inputMatches)
|
|
137
|
+
throw new Error("ChatGPT 输入内容校验失败,未发送消息");
|
|
138
|
+
const sent = await this.evaluate(`(() => {
|
|
139
|
+
const editor = document.querySelector(${JSON.stringify(exports.editorSelector)});
|
|
140
|
+
const findButton = () => {
|
|
141
|
+
for (let parent = editor; parent && parent !== document.body; parent = parent.parentElement) {
|
|
142
|
+
const buttons = [...parent.querySelectorAll(${JSON.stringify(sendButtonSelector)})];
|
|
143
|
+
if (buttons.length === 1) return buttons[0];
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
};
|
|
147
|
+
const button = findButton();
|
|
148
|
+
if (!button || button.disabled) return false;
|
|
149
|
+
button.click();
|
|
150
|
+
return true;
|
|
151
|
+
})()`);
|
|
152
|
+
if (!sent)
|
|
153
|
+
throw new Error("ChatGPT 桌面端发送按钮不可用,未确认消息已发送");
|
|
154
|
+
const deadline = Date.now() + timeoutMs;
|
|
155
|
+
let previous = "";
|
|
156
|
+
let stable = 0;
|
|
157
|
+
while (Date.now() < deadline) {
|
|
158
|
+
await sleep(800);
|
|
159
|
+
const raw = await this.evaluate(`(() => {
|
|
160
|
+
const before = ${JSON.stringify(beforeTurns)};
|
|
161
|
+
const turns = [...document.querySelectorAll(${JSON.stringify(exports.turnSelector)})];
|
|
162
|
+
const turn = turns.findLast((item, index) => {
|
|
163
|
+
const id = item.getAttribute(${JSON.stringify(turnIdAttribute)});
|
|
164
|
+
const oldIndex = before.findIndex(entry => entry.id === id);
|
|
165
|
+
const old = oldIndex < 0 ? undefined : before[oldIndex];
|
|
166
|
+
const users = item.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)});
|
|
167
|
+
const fresh = oldIndex < 0 || index >= before.length || users.length > (old?.users ?? 0);
|
|
168
|
+
return fresh && users.item(users.length - 1)?.innerText.trim() === ${JSON.stringify(text.trim())};
|
|
169
|
+
});
|
|
170
|
+
if (!turn) {
|
|
171
|
+
const assistants = [...document.querySelectorAll(${JSON.stringify(assistantMessageSelector)})];
|
|
172
|
+
const fallback = assistants.length > ${beforeAssistantCount}
|
|
173
|
+
? assistants.at(-1)?.innerText.trim() ?? ""
|
|
174
|
+
: "";
|
|
175
|
+
return JSON.stringify({ reply: fallback, active: Boolean(document.querySelector(${JSON.stringify(stopButtonSelector)})) });
|
|
176
|
+
}
|
|
177
|
+
const user = turn.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)}).item(turn.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)}).length - 1);
|
|
178
|
+
const unit = user?.closest(${JSON.stringify(contentUnitSelector)});
|
|
179
|
+
const units = [...turn.querySelectorAll(${JSON.stringify(contentUnitSelector)})];
|
|
180
|
+
const index = unit ? units.indexOf(unit) : -1;
|
|
181
|
+
const replies = index < 0 ? [] : units.slice(index + 1).map(item => item.innerText.trim()).filter(Boolean);
|
|
182
|
+
const legacyReplies = [...turn.querySelectorAll('[data-message-author-role="assistant"]')].map(item => item.innerText.trim()).filter(Boolean);
|
|
183
|
+
const reply = replies.at(-1) ?? legacyReplies.at(-1) ?? "";
|
|
184
|
+
const active = Boolean(document.querySelector(${JSON.stringify(stopButtonSelector)}));
|
|
185
|
+
return JSON.stringify({ reply, active });
|
|
186
|
+
})()`);
|
|
187
|
+
const state = JSON.parse(String(raw));
|
|
188
|
+
const reply = state.reply.trim();
|
|
189
|
+
stable = reply && reply === previous ? stable + 1 : 0;
|
|
190
|
+
previous = reply;
|
|
191
|
+
if (reply && stable >= 2 && !state.active)
|
|
192
|
+
return reply;
|
|
193
|
+
}
|
|
194
|
+
throw new Error("ChatGPT UI 回复超时");
|
|
195
|
+
}
|
|
196
|
+
async startTunnel() {
|
|
197
|
+
const ssh = this.ssh;
|
|
198
|
+
const args = [
|
|
199
|
+
"-N", "-o", "BatchMode=yes", "-o", "ExitOnForwardFailure=yes", "-o", "ConnectTimeout=10",
|
|
200
|
+
"-L", `${ssh.localPort}:127.0.0.1:${ssh.remotePort}`, "-p", String(ssh.port ?? 22),
|
|
201
|
+
];
|
|
202
|
+
if (ssh.keyPath)
|
|
203
|
+
args.push("-i", ssh.keyPath);
|
|
204
|
+
args.push(`${ssh.user}@${ssh.host}`);
|
|
205
|
+
this.tunnel = (0, node_child_process_1.spawn)("ssh", args, { stdio: "ignore", windowsHide: true });
|
|
206
|
+
const tunnel = this.tunnel;
|
|
207
|
+
await new Promise((resolve, reject) => {
|
|
208
|
+
let ready = false;
|
|
209
|
+
const fail = (error) => {
|
|
210
|
+
if (!ready)
|
|
211
|
+
reject(error);
|
|
212
|
+
else if (this.tunnel === tunnel) {
|
|
213
|
+
this.tunnel = undefined;
|
|
214
|
+
this.socket?.close();
|
|
215
|
+
this.socket = undefined;
|
|
216
|
+
this.rejectPending(error);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
const timer = setTimeout(() => {
|
|
220
|
+
ready = true;
|
|
221
|
+
resolve();
|
|
222
|
+
}, 500);
|
|
223
|
+
tunnel.once("error", (error) => {
|
|
224
|
+
clearTimeout(timer);
|
|
225
|
+
fail(error);
|
|
226
|
+
});
|
|
227
|
+
tunnel.once("exit", () => {
|
|
228
|
+
clearTimeout(timer);
|
|
229
|
+
fail(new Error("SSH CDP 隧道已关闭,请检查远程连接"));
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
async waitFor(expression, message) {
|
|
234
|
+
const deadline = Date.now() + this.timeoutMs;
|
|
235
|
+
while (Date.now() < deadline) {
|
|
236
|
+
if (await this.evaluate(expression))
|
|
237
|
+
return;
|
|
238
|
+
await sleep(200);
|
|
239
|
+
}
|
|
240
|
+
throw new Error(message);
|
|
241
|
+
}
|
|
242
|
+
rejectPending(error) {
|
|
243
|
+
for (const pending of this.pending.values())
|
|
244
|
+
pending.reject(error);
|
|
245
|
+
this.pending.clear();
|
|
246
|
+
}
|
|
247
|
+
onMessage(raw) {
|
|
248
|
+
let message;
|
|
249
|
+
try {
|
|
250
|
+
message = JSON.parse(raw);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (message.id === undefined)
|
|
256
|
+
return;
|
|
257
|
+
const pending = this.pending.get(message.id);
|
|
258
|
+
if (!pending)
|
|
259
|
+
return;
|
|
260
|
+
this.pending.delete(message.id);
|
|
261
|
+
if (message.error)
|
|
262
|
+
pending.reject(new Error(message.error.message ?? "CDP 调用失败"));
|
|
263
|
+
else
|
|
264
|
+
pending.resolve(message);
|
|
265
|
+
}
|
|
266
|
+
async cdp(method, params = {}) {
|
|
267
|
+
if (!this.socket)
|
|
268
|
+
throw new Error("ChatGPT CDP 尚未连接");
|
|
269
|
+
const id = ++this.sequence;
|
|
270
|
+
const socket = this.socket;
|
|
271
|
+
return new Promise((resolve, reject) => {
|
|
272
|
+
const timer = setTimeout(() => {
|
|
273
|
+
this.pending.delete(id);
|
|
274
|
+
reject(new Error(`CDP ${method} 超时`));
|
|
275
|
+
}, this.timeoutMs);
|
|
276
|
+
this.pending.set(id, {
|
|
277
|
+
resolve: (value) => {
|
|
278
|
+
clearTimeout(timer);
|
|
279
|
+
resolve(value);
|
|
280
|
+
},
|
|
281
|
+
reject: (error) => {
|
|
282
|
+
clearTimeout(timer);
|
|
283
|
+
reject(error);
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
try {
|
|
287
|
+
socket.send(JSON.stringify({ id, method, params }));
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
this.pending.delete(id);
|
|
291
|
+
clearTimeout(timer);
|
|
292
|
+
reject(error);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
async evaluate(expression) {
|
|
297
|
+
const response = await this.cdp("Runtime.evaluate", { expression, returnByValue: true, awaitPromise: true });
|
|
298
|
+
if (response.result?.exceptionDetails) {
|
|
299
|
+
throw new Error(`ChatGPT 页面 JS 异常:${JSON.stringify(response.result.exceptionDetails).slice(0, 300)}`);
|
|
300
|
+
}
|
|
301
|
+
return response.result?.result?.value;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
exports.ChatgptCdpClient = ChatgptCdpClient;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ChatgptCdpClient = exports.ChatgptPlugin = exports.chatgptManifest = void 0;
|
|
4
|
+
const cdp_client_1 = require("./cdp-client");
|
|
5
|
+
exports.chatgptManifest = {
|
|
6
|
+
id: "chatgpt",
|
|
7
|
+
version: "1.0.0",
|
|
8
|
+
displayName: "ChatGPT(Codex)",
|
|
9
|
+
type: "agent",
|
|
10
|
+
entry: "dist/index.js",
|
|
11
|
+
apiVersion: "1",
|
|
12
|
+
process: "in-process",
|
|
13
|
+
capabilities: ["chat", "health"],
|
|
14
|
+
configSchema: "config.schema.json",
|
|
15
|
+
};
|
|
16
|
+
function port(value, fallback) {
|
|
17
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 65_535
|
|
18
|
+
? value
|
|
19
|
+
: fallback;
|
|
20
|
+
}
|
|
21
|
+
class ChatgptPlugin {
|
|
22
|
+
options;
|
|
23
|
+
manifest = exports.chatgptManifest;
|
|
24
|
+
client;
|
|
25
|
+
failure;
|
|
26
|
+
remote = false;
|
|
27
|
+
queue = Promise.resolve();
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
this.options = options;
|
|
30
|
+
}
|
|
31
|
+
async activate(context) {
|
|
32
|
+
const config = context.config;
|
|
33
|
+
const hasSshHost = config.sshHost !== undefined;
|
|
34
|
+
const hasSshUser = config.sshUser !== undefined;
|
|
35
|
+
if (hasSshHost !== hasSshUser || (hasSshHost && (typeof config.sshHost !== "string" || !config.sshHost.trim()
|
|
36
|
+
|| typeof config.sshUser !== "string" || !config.sshUser.trim()))) {
|
|
37
|
+
throw new Error("sshHost 和 sshUser 必须同时填写有效值");
|
|
38
|
+
}
|
|
39
|
+
if (config.sshKeyPath !== undefined && typeof config.sshKeyPath !== "string") {
|
|
40
|
+
throw new Error("sshKeyPath 必须是字符串");
|
|
41
|
+
}
|
|
42
|
+
const ssh = hasSshHost ? {
|
|
43
|
+
host: config.sshHost.trim(),
|
|
44
|
+
user: config.sshUser.trim(),
|
|
45
|
+
port: port(config.sshPort, 22),
|
|
46
|
+
keyPath: typeof config.sshKeyPath === "string" ? config.sshKeyPath : undefined,
|
|
47
|
+
remotePort: port(config.remoteCdpPort, 9230),
|
|
48
|
+
localPort: port(config.localCdpPort, 9230),
|
|
49
|
+
} : undefined;
|
|
50
|
+
this.remote = Boolean(ssh);
|
|
51
|
+
this.failure = undefined;
|
|
52
|
+
this.client = this.options.client ?? new cdp_client_1.ChatgptCdpClient({
|
|
53
|
+
port: port(config.localCdpPort, 9230),
|
|
54
|
+
timeoutMs: typeof config.timeoutMs === "number" && config.timeoutMs > 0 ? config.timeoutMs : 30_000,
|
|
55
|
+
ssh,
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
await this.client.connect();
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
this.failure = error instanceof Error ? error.message : String(error);
|
|
62
|
+
context.logger.warn(this.failure);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async deactivate() {
|
|
67
|
+
await this.queue;
|
|
68
|
+
this.client?.close();
|
|
69
|
+
this.client = undefined;
|
|
70
|
+
this.remote = false;
|
|
71
|
+
}
|
|
72
|
+
async health() {
|
|
73
|
+
return this.client?.connected()
|
|
74
|
+
? { status: "ready", details: { transport: "cdp", remote: this.remote } }
|
|
75
|
+
: { status: "failed", message: this.failure ?? "ChatGPT CDP 未连接", details: { transport: "cdp", remote: this.remote } };
|
|
76
|
+
}
|
|
77
|
+
async handle(request) {
|
|
78
|
+
if (!request.task.trim())
|
|
79
|
+
throw new Error("消息内容不能为空");
|
|
80
|
+
if (!this.client?.connected())
|
|
81
|
+
throw new Error("ChatGPT CDP 未连接");
|
|
82
|
+
const run = this.queue.then(() => this.client.direct(request.task, request.timeoutMs));
|
|
83
|
+
this.queue = run.then(() => undefined, () => undefined);
|
|
84
|
+
return { text: await run, sessionId: request.sessionId };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.ChatgptPlugin = ChatgptPlugin;
|
|
88
|
+
var cdp_client_2 = require("./cdp-client");
|
|
89
|
+
Object.defineProperty(exports, "ChatgptCdpClient", { enumerable: true, get: function () { return cdp_client_2.ChatgptCdpClient; } });
|
|
90
|
+
exports.default = ChatgptPlugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "agent-bridge-chatgpt",
|
|
3
|
-
"version": "0.0.0-stage",
|
|
4
|
-
"stub": true,
|
|
5
|
-
"description": "Temporary package placeholder for staged publishing"
|
|
6
|
-
}
|
|
1
|
+
{"name":"agent-bridge-chatgpt","version":"1.0.0","description":"agent-bridge 的 ChatGPT Codex CDP 插件。","keywords":["mu-agent-bridge-plugin","mu-agent-bridge-agent"],"main":"dist/src-ts/plugins/chatgpt/index.js","files":["dist/","plugin.json","config.schema.json","README.md"],"agentBridge":{"plugin":"./plugin.json","type":"agent"},"engines":{"node":">=22"}}
|
package/plugin.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"chatgpt","version":"1.0.0","type":"agent","displayName":"ChatGPT(Codex)","entry":"dist/src-ts/plugins/chatgpt/index.js","apiVersion":"1","process":"in-process","capabilities":["chat","health"],"configSchema":"config.schema.json"}
|