@next-open-ai/openclawx 0.8.8 → 0.8.16

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 (32) hide show
  1. package/README.md +28 -21
  2. package/apps/desktop/renderer/dist/assets/index-DmIfN-Vc.js +89 -0
  3. package/apps/desktop/renderer/dist/assets/index-DvB8yW8I.css +10 -0
  4. package/apps/desktop/renderer/dist/index.html +2 -2
  5. package/dist/core/agent/proxy/adapters/coze-adapter.d.ts +2 -0
  6. package/dist/core/agent/proxy/adapters/coze-adapter.js +399 -0
  7. package/dist/core/agent/proxy/adapters/local-adapter.d.ts +2 -0
  8. package/dist/core/agent/proxy/adapters/local-adapter.js +100 -0
  9. package/dist/core/agent/proxy/adapters/openclawx-adapter.d.ts +2 -0
  10. package/dist/core/agent/proxy/adapters/openclawx-adapter.js +108 -0
  11. package/dist/core/agent/proxy/index.d.ts +3 -0
  12. package/dist/core/agent/proxy/index.js +14 -0
  13. package/dist/core/agent/proxy/registry.d.ts +7 -0
  14. package/dist/core/agent/proxy/registry.js +13 -0
  15. package/dist/core/agent/proxy/run-for-channel.d.ts +3 -0
  16. package/dist/core/agent/proxy/run-for-channel.js +31 -0
  17. package/dist/core/agent/proxy/types.d.ts +28 -0
  18. package/dist/core/agent/proxy/types.js +1 -0
  19. package/dist/core/config/desktop-config.d.ts +38 -0
  20. package/dist/core/config/desktop-config.js +64 -1
  21. package/dist/gateway/channel/adapters/telegram.js +13 -2
  22. package/dist/gateway/channel/run-agent.d.ts +2 -4
  23. package/dist/gateway/channel/run-agent.js +13 -125
  24. package/dist/gateway/methods/agent-chat.js +34 -0
  25. package/dist/server/agent-config/agent-config.controller.d.ts +1 -1
  26. package/dist/server/agent-config/agent-config.service.d.ts +27 -1
  27. package/dist/server/agent-config/agent-config.service.js +43 -0
  28. package/dist/server/agents/agents.controller.d.ts +16 -0
  29. package/dist/server/agents/agents.controller.js +62 -1
  30. package/package.json +1 -1
  31. package/apps/desktop/renderer/dist/assets/index-D9ET31hp.css +0 -10
  32. package/apps/desktop/renderer/dist/assets/index-DI___vdo.js +0 -89
@@ -0,0 +1,399 @@
1
+ const COZE_ENDPOINT_COM = "https://api.coze.com";
2
+ const COZE_ENDPOINT_CN = "https://api.coze.cn";
3
+ const REQUEST_TIMEOUT_MS = 120_000;
4
+ function getCozeConfig(config) {
5
+ const coze = config.coze;
6
+ if (!coze?.botId?.trim() || !coze?.apiKey?.trim()) {
7
+ console.warn(`[Coze] getCozeConfig: missing coze config (hasCoze=${!!coze}, hasBotId=${!!coze?.botId?.trim()}, hasApiKey=${!!coze?.apiKey?.trim()})`);
8
+ return null;
9
+ }
10
+ const explicitEndpoint = coze.endpoint?.trim();
11
+ const region = coze.region === "cn" ? "cn" : "com";
12
+ const defaultBase = region === "cn" ? COZE_ENDPOINT_CN : COZE_ENDPOINT_COM;
13
+ const baseUrl = (explicitEndpoint || defaultBase).replace(/\/$/, "");
14
+ const apiKey = coze.apiKey.trim();
15
+ console.log(`[Coze] getCozeConfig: botId=${coze.botId.trim()}, region=${region}, baseUrl=${baseUrl}`);
16
+ return { botId: coze.botId.trim(), apiKey, baseUrl, region };
17
+ }
18
+ /** 国内站常见内部事件:不当作正文展示 */
19
+ const COZE_INTERNAL_MSG_TYPES = new Set([
20
+ "empty result",
21
+ "generate_answer_finish",
22
+ ]);
23
+ /** 若字符串明显是内部协议 JSON(不应展示给用户),返回 true */
24
+ function looksLikeInternalJson(s) {
25
+ const t = s.trim();
26
+ if (t.startsWith("{")) {
27
+ if (t.includes('"msg_type"') || t.includes("from_module") || t.includes("generate_answer_finish"))
28
+ return true;
29
+ }
30
+ return false;
31
+ }
32
+ /** 从 Coze 流式 SSE 的 data 行 JSON 中解析出文本 delta(兼容国际站/国内站 v3) */
33
+ function parseCozeStreamLine(data) {
34
+ const trimmed = data.trim();
35
+ if (!trimmed || trimmed === "[DONE]" || trimmed === "[done]")
36
+ return null;
37
+ try {
38
+ const parsed = JSON.parse(trimmed);
39
+ const msgType = parsed.msg_type;
40
+ if (msgType != null && COZE_INTERNAL_MSG_TYPES.has(String(msgType).toLowerCase()))
41
+ return null;
42
+ const d = parsed.data;
43
+ if (d && typeof d.content === "string") {
44
+ if (!looksLikeInternalJson(d.content))
45
+ return d.content;
46
+ return null;
47
+ }
48
+ if (d && typeof d.delta === "string") {
49
+ const v = d.delta;
50
+ if (!looksLikeInternalJson(v))
51
+ return v;
52
+ return null;
53
+ }
54
+ if (d && d.delta?.content != null) {
55
+ const v = String(d.delta.content);
56
+ if (!looksLikeInternalJson(v))
57
+ return v;
58
+ return null;
59
+ }
60
+ const dataMsg = d?.message;
61
+ if (dataMsg && typeof dataMsg.content === "string") {
62
+ if (!looksLikeInternalJson(dataMsg.content))
63
+ return dataMsg.content;
64
+ return null;
65
+ }
66
+ const msg = parsed.message;
67
+ if (msg && typeof msg.content === "string") {
68
+ if (!looksLikeInternalJson(msg.content))
69
+ return msg.content;
70
+ return null;
71
+ }
72
+ if (typeof parsed.content === "string") {
73
+ const v = parsed.content;
74
+ if (!looksLikeInternalJson(v))
75
+ return v;
76
+ return null;
77
+ }
78
+ if (typeof parsed.delta === "string") {
79
+ const v = parsed.delta;
80
+ if (!looksLikeInternalJson(v))
81
+ return v;
82
+ return null;
83
+ }
84
+ if (parsed.delta?.content != null) {
85
+ const v = String(parsed.delta.content);
86
+ if (!looksLikeInternalJson(v))
87
+ return v;
88
+ return null;
89
+ }
90
+ if (parsed.type === "answer" && typeof parsed.content === "string") {
91
+ const v = parsed.content;
92
+ if (!looksLikeInternalJson(v))
93
+ return v;
94
+ return null;
95
+ }
96
+ if (typeof parsed.answer === "string") {
97
+ const v = parsed.answer;
98
+ if (!looksLikeInternalJson(v))
99
+ return v;
100
+ return null;
101
+ }
102
+ if (typeof parsed.text === "string") {
103
+ const v = parsed.text;
104
+ if (!looksLikeInternalJson(v))
105
+ return v;
106
+ return null;
107
+ }
108
+ if (d && typeof d.text === "string") {
109
+ const v = d.text;
110
+ if (!looksLikeInternalJson(v))
111
+ return v;
112
+ return null;
113
+ }
114
+ const parts = (msg ?? dataMsg)?.parts;
115
+ if (Array.isArray(parts) && parts.length > 0) {
116
+ const text = parts.map((p) => (p?.content != null ? String(p.content) : "")).join("");
117
+ if (text && !looksLikeInternalJson(text))
118
+ return text;
119
+ }
120
+ return null;
121
+ }
122
+ catch {
123
+ return null;
124
+ }
125
+ }
126
+ /** Coze 业务错误码:code !== 0 时抛出,提示用户检查配置(如 API Key) */
127
+ function throwIfCozeError(data) {
128
+ const code = data.code;
129
+ if (code === undefined || code === 0)
130
+ return;
131
+ const msg = data.msg || "Unknown error";
132
+ if (code === 4101) {
133
+ throw new Error("Coze 返回 4101:当前填写的 API Key 被 Coze 拒绝(已传递,但认证失败)。请检查:1) 必须使用「个人访问令牌」Personal Access Token,在 Coze 开放平台「个人中心 - API 密钥」创建,不要用 Bot ID 或其它 token;2) 国际站 (api.coze.com) 与国内站 (api.coze.cn) 的 Key 不通用,Endpoint 需与 Key 所属站点一致;3) Key 可能已过期或被撤销,请重新生成并保存。参考:https://www.coze.com/docs/developer_guides/authentication");
134
+ }
135
+ throw new Error(`Coze API 错误 (code=${code}): ${msg}`);
136
+ }
137
+ /** 从 Coze 非流式 JSON 响应中解析出助手回复文本 */
138
+ function parseCozeCollectResponse(data) {
139
+ const dataObj = data.data;
140
+ if (dataObj) {
141
+ const msg = dataObj.message;
142
+ if (msg && typeof msg.content === "string")
143
+ return msg.content.trim();
144
+ if (typeof dataObj.content === "string")
145
+ return dataObj.content.trim();
146
+ const messages = dataObj.messages;
147
+ if (Array.isArray(messages) && messages.length > 0) {
148
+ const last = messages[messages.length - 1];
149
+ if (last && typeof last.content === "string")
150
+ return last.content.trim();
151
+ }
152
+ }
153
+ const msg = data.message;
154
+ if (msg && typeof msg.content === "string")
155
+ return msg.content.trim();
156
+ if (typeof data.content === "string")
157
+ return data.content.trim();
158
+ return "";
159
+ }
160
+ export const cozeAdapter = {
161
+ type: "coze",
162
+ async runStream(options, config, callbacks) {
163
+ const cozeConfig = getCozeConfig(config);
164
+ if (!cozeConfig) {
165
+ throw new Error("Coze adapter: missing coze.botId or coze.apiKey in agent config");
166
+ }
167
+ const { sessionId, message } = options;
168
+ const url = `${cozeConfig.baseUrl}/v3/chat?bot_id=${encodeURIComponent(cozeConfig.botId)}`;
169
+ const body = {
170
+ conversation_id: sessionId,
171
+ bot_id: cozeConfig.botId,
172
+ user_id: `channel:${sessionId}`,
173
+ stream: true,
174
+ auto_save_history: true,
175
+ additional_messages: [{ role: "user", content: message, content_type: "text" }],
176
+ };
177
+ const controller = new AbortController();
178
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
179
+ try {
180
+ console.log(`[Coze] POST ${url} (stream=true)`);
181
+ const res = await fetch(url, {
182
+ method: "POST",
183
+ headers: {
184
+ Authorization: `Bearer ${cozeConfig.apiKey}`,
185
+ "Content-Type": "application/json",
186
+ },
187
+ body: JSON.stringify(body),
188
+ signal: controller.signal,
189
+ });
190
+ console.log(`[Coze] Response status=${res.status} content-type=${res.headers.get("content-type") ?? "(none)"}`);
191
+ if (!res.ok) {
192
+ const text = await res.text();
193
+ throw new Error(`Coze API error ${res.status}: ${text}`);
194
+ }
195
+ const contentType = (res.headers.get("content-type") || "").toLowerCase();
196
+ // Coze 在 token 错误等情况下仍返回 200 + application/json,需先按 JSON 解析并检查 code
197
+ if (contentType.includes("application/json")) {
198
+ const text = await res.text();
199
+ try {
200
+ const data = JSON.parse(text);
201
+ throwIfCozeError(data);
202
+ const fullText = parseCozeCollectResponse(data);
203
+ if (fullText)
204
+ callbacks.onChunk(fullText);
205
+ }
206
+ catch (e) {
207
+ if (e?.message?.startsWith("Coze API") || e?.message?.startsWith("Coze API Key"))
208
+ throw e;
209
+ throw new Error(`Coze 返回了 JSON 但解析失败: ${e?.message ?? e}`);
210
+ }
211
+ callbacks.onTurnEnd?.();
212
+ callbacks.onDone();
213
+ return;
214
+ }
215
+ const reader = res.body?.getReader();
216
+ if (!reader) {
217
+ console.log(`[Coze] No stream reader, reading body as text`);
218
+ const text = await res.text();
219
+ if (text)
220
+ callbacks.onChunk(text);
221
+ callbacks.onTurnEnd?.();
222
+ callbacks.onDone();
223
+ return;
224
+ }
225
+ const decoder = new TextDecoder();
226
+ let buffer = "";
227
+ let hadAnyChunk = false;
228
+ const rawDataSamples = [];
229
+ let lastEvent = "";
230
+ let streamAccumulated = "";
231
+ while (true) {
232
+ const { done, value } = await reader.read();
233
+ if (done)
234
+ break;
235
+ buffer += decoder.decode(value, { stream: true });
236
+ const lines = buffer.replace(/\r\n/g, "\n").split("\n");
237
+ buffer = lines.pop() || "";
238
+ for (const line of lines) {
239
+ const trimmedLine = line.trim();
240
+ if (trimmedLine.startsWith("event:")) {
241
+ lastEvent = trimmedLine.slice(6).trim();
242
+ continue;
243
+ }
244
+ if (!trimmedLine.startsWith("data:"))
245
+ continue;
246
+ const raw = trimmedLine.slice(5).trim();
247
+ if (!raw)
248
+ continue;
249
+ if (rawDataSamples.length < 5)
250
+ rawDataSamples.push(lastEvent + " -> " + raw.slice(0, 150));
251
+ const e = lastEvent.toLowerCase();
252
+ const isMessageEvent = e.includes("message") || e.includes("delta") || e.includes("answer") || e.includes("chunk") || lastEvent === "";
253
+ const content = isMessageEvent ? parseCozeStreamLine(raw) : null;
254
+ if (content) {
255
+ if (content === streamAccumulated)
256
+ continue;
257
+ if (streamAccumulated.length > 0 && streamAccumulated.startsWith(content))
258
+ continue;
259
+ if (streamAccumulated.length > 0 && content.length >= 10 && streamAccumulated.endsWith(content))
260
+ continue;
261
+ if (content.startsWith(streamAccumulated)) {
262
+ const suffix = content.slice(streamAccumulated.length);
263
+ if (suffix) {
264
+ streamAccumulated = content;
265
+ hadAnyChunk = true;
266
+ callbacks.onChunk(suffix);
267
+ }
268
+ continue;
269
+ }
270
+ streamAccumulated += content;
271
+ hadAnyChunk = true;
272
+ callbacks.onChunk(content);
273
+ }
274
+ }
275
+ }
276
+ // 若 SSE 流中没有任何可解析的 content,尝试:1) buffer 为 data: 行;2) buffer 为整段 JSON(非流式返回)
277
+ if (!hadAnyChunk && buffer.trim()) {
278
+ const dataPart = buffer.startsWith("data: ") ? buffer.slice(6) : buffer;
279
+ const content = parseCozeStreamLine(dataPart);
280
+ if (content) {
281
+ callbacks.onChunk(content);
282
+ }
283
+ else {
284
+ try {
285
+ const asJson = JSON.parse(buffer.trim());
286
+ const fullText = parseCozeCollectResponse(asJson);
287
+ if (fullText)
288
+ callbacks.onChunk(fullText);
289
+ }
290
+ catch {
291
+ // ignore
292
+ }
293
+ }
294
+ }
295
+ if (!hadAnyChunk) {
296
+ const sample = buffer.trim().slice(0, 300);
297
+ console.log(`[Coze] Stream ended with no parsed chunks. Buffer sample: ${sample || "(empty)"}`);
298
+ if (rawDataSamples.length > 0) {
299
+ console.log(`[Coze] First SSE data lines sample: ${rawDataSamples.join(" | ")}`);
300
+ }
301
+ // 可能是 Coze 返回的 JSON 错误体(如 4101 token 无效),先检查再决定是否回退
302
+ try {
303
+ const asJson = JSON.parse(buffer.trim());
304
+ throwIfCozeError(asJson);
305
+ const fullText = parseCozeCollectResponse(asJson);
306
+ if (fullText)
307
+ callbacks.onChunk(fullText);
308
+ }
309
+ catch (parseErr) {
310
+ if (parseErr?.message?.startsWith("Coze API") || parseErr?.message?.startsWith("Coze API Key"))
311
+ throw parseErr;
312
+ // 流式未解析到内容,回退到非流式接口
313
+ try {
314
+ console.log(`[Coze] Stream had no parsed chunks, trying runCollect fallback`);
315
+ const fallbackText = await this.runCollect(options, config);
316
+ if (fallbackText) {
317
+ callbacks.onChunk(fallbackText);
318
+ console.log(`[Coze] Fallback runCollect returned ${fallbackText.length} chars`);
319
+ }
320
+ }
321
+ catch (collectErr) {
322
+ if (collectErr?.message?.startsWith("Coze API") || collectErr?.message?.startsWith("Coze API Key"))
323
+ throw collectErr;
324
+ console.warn(`[Coze] Fallback runCollect failed:`, collectErr?.message ?? collectErr);
325
+ }
326
+ }
327
+ }
328
+ callbacks.onTurnEnd?.();
329
+ callbacks.onDone();
330
+ console.log(`[Coze] runStream finished`);
331
+ }
332
+ catch (err) {
333
+ console.error(`[Coze] runStream error:`, err?.message ?? err);
334
+ // 请求超时或网络错误时,尝试非流式接口兜底
335
+ try {
336
+ const fallbackText = await this.runCollect(options, config);
337
+ if (fallbackText) {
338
+ callbacks.onChunk(fallbackText);
339
+ callbacks.onTurnEnd?.();
340
+ callbacks.onDone();
341
+ console.log(`[Coze] runStream recovered via runCollect`);
342
+ return;
343
+ }
344
+ }
345
+ catch (_) {
346
+ // ignore
347
+ }
348
+ throw err;
349
+ }
350
+ finally {
351
+ clearTimeout(timeoutId);
352
+ }
353
+ },
354
+ async runCollect(options, config) {
355
+ const cozeConfig = getCozeConfig(config);
356
+ if (!cozeConfig) {
357
+ throw new Error("Coze adapter: missing coze.botId or coze.apiKey in agent config");
358
+ }
359
+ const { sessionId, message } = options;
360
+ const url = `${cozeConfig.baseUrl}/v3/chat?bot_id=${encodeURIComponent(cozeConfig.botId)}`;
361
+ const body = {
362
+ conversation_id: sessionId,
363
+ bot_id: cozeConfig.botId,
364
+ user_id: `channel:${sessionId}`,
365
+ stream: false,
366
+ auto_save_history: true,
367
+ additional_messages: [{ role: "user", content: message, content_type: "text" }],
368
+ };
369
+ const controller = new AbortController();
370
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
371
+ try {
372
+ const res = await fetch(url, {
373
+ method: "POST",
374
+ headers: {
375
+ Authorization: `Bearer ${cozeConfig.apiKey}`,
376
+ "Content-Type": "application/json",
377
+ },
378
+ body: JSON.stringify(body),
379
+ signal: controller.signal,
380
+ });
381
+ const raw = await res.text();
382
+ if (!res.ok)
383
+ throw new Error(`Coze API error ${res.status}: ${raw}`);
384
+ let data;
385
+ try {
386
+ data = JSON.parse(raw);
387
+ }
388
+ catch {
389
+ return "(无文本回复)";
390
+ }
391
+ throwIfCozeError(data);
392
+ const content = parseCozeCollectResponse(data);
393
+ return content || "(无文本回复)";
394
+ }
395
+ finally {
396
+ clearTimeout(timeoutId);
397
+ }
398
+ },
399
+ };
@@ -0,0 +1,2 @@
1
+ import type { IAgentProxyAdapter } from "../types.js";
2
+ export declare const localAdapter: IAgentProxyAdapter;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Local AgentProxy 适配器:使用本机 AgentManager(pi-coding-agent)执行。
3
+ */
4
+ import { agentManager } from "../../agent-manager.js";
5
+ import { getDesktopConfig } from "../../../config/desktop-config.js";
6
+ import { getExperienceContextForUserMessage } from "../../../memory/index.js";
7
+ const CHANNEL_AGENT_TIMEOUT_MS = 120_000;
8
+ export const localAdapter = {
9
+ type: "local",
10
+ async runStream(options, config, callbacks) {
11
+ const { sessionId, message, agentId } = options;
12
+ const workspace = config.workspace ?? agentId ?? "default";
13
+ const { maxAgentSessions } = getDesktopConfig();
14
+ const session = await agentManager.getOrCreateSession(sessionId, {
15
+ agentId,
16
+ workspace,
17
+ provider: config.provider,
18
+ modelId: config.model,
19
+ apiKey: config.apiKey,
20
+ maxSessions: maxAgentSessions,
21
+ targetAgentId: agentId,
22
+ mcpServers: config.mcpServers,
23
+ systemPrompt: config.systemPrompt,
24
+ });
25
+ let resolveDone;
26
+ const donePromise = new Promise((r) => {
27
+ resolveDone = r;
28
+ });
29
+ const unsubscribe = session.subscribe((event) => {
30
+ if (event.type === "message_update" && event.assistantMessageEvent?.type === "text_delta" && event.assistantMessageEvent?.delta) {
31
+ callbacks.onChunk(event.assistantMessageEvent.delta);
32
+ }
33
+ else if (event.type === "turn_end") {
34
+ callbacks.onTurnEnd?.();
35
+ }
36
+ else if (event.type === "agent_end") {
37
+ callbacks.onDone();
38
+ resolveDone();
39
+ }
40
+ });
41
+ try {
42
+ const experienceBlock = await getExperienceContextForUserMessage();
43
+ const userMessageToSend = experienceBlock.trim().length > 0
44
+ ? `${experienceBlock}\n\n用户问题:\n${message}`
45
+ : message;
46
+ await session.sendUserMessage(userMessageToSend, { deliverAs: "followUp" });
47
+ await Promise.race([
48
+ donePromise,
49
+ new Promise((_, rej) => setTimeout(() => rej(new Error("Channel agent reply timeout")), CHANNEL_AGENT_TIMEOUT_MS)),
50
+ ]);
51
+ }
52
+ finally {
53
+ unsubscribe();
54
+ }
55
+ },
56
+ async runCollect(options, config) {
57
+ const { sessionId, message, agentId } = options;
58
+ const workspace = config.workspace ?? agentId ?? "default";
59
+ const { maxAgentSessions } = getDesktopConfig();
60
+ const session = await agentManager.getOrCreateSession(sessionId, {
61
+ agentId,
62
+ workspace,
63
+ provider: config.provider,
64
+ modelId: config.model,
65
+ apiKey: config.apiKey,
66
+ maxSessions: maxAgentSessions,
67
+ targetAgentId: agentId,
68
+ mcpServers: config.mcpServers,
69
+ systemPrompt: config.systemPrompt,
70
+ });
71
+ const chunks = [];
72
+ let resolveDone;
73
+ const donePromise = new Promise((r) => {
74
+ resolveDone = r;
75
+ });
76
+ const unsubscribe = session.subscribe((event) => {
77
+ if (event.type === "message_update" && event.assistantMessageEvent?.type === "text_delta" && event.assistantMessageEvent?.delta) {
78
+ chunks.push(event.assistantMessageEvent.delta);
79
+ }
80
+ else if (event.type === "agent_end") {
81
+ resolveDone();
82
+ }
83
+ });
84
+ try {
85
+ const experienceBlock = await getExperienceContextForUserMessage();
86
+ const userMessageToSend = experienceBlock.trim().length > 0
87
+ ? `${experienceBlock}\n\n用户问题:\n${message}`
88
+ : message;
89
+ await session.sendUserMessage(userMessageToSend, { deliverAs: "followUp" });
90
+ await Promise.race([
91
+ donePromise,
92
+ new Promise((_, rej) => setTimeout(() => rej(new Error("Channel agent reply timeout")), CHANNEL_AGENT_TIMEOUT_MS)),
93
+ ]);
94
+ }
95
+ finally {
96
+ unsubscribe();
97
+ }
98
+ return chunks.join("").trim() || "(无文本回复)";
99
+ },
100
+ };
@@ -0,0 +1,2 @@
1
+ import type { IAgentProxyAdapter } from "../types.js";
2
+ export declare const openclawxAdapter: IAgentProxyAdapter;
@@ -0,0 +1,108 @@
1
+ const REQUEST_TIMEOUT_MS = 120_000;
2
+ function getOpenClawXConfig(config) {
3
+ const ox = config.openclawx;
4
+ if (!ox?.baseUrl?.trim())
5
+ return null;
6
+ return {
7
+ baseUrl: ox.baseUrl.replace(/\/$/, ""),
8
+ apiKey: ox.apiKey?.trim(),
9
+ };
10
+ }
11
+ function buildHeaders(apiKey) {
12
+ const headers = { "Content-Type": "application/json" };
13
+ if (apiKey)
14
+ headers["Authorization"] = `Bearer ${apiKey}`;
15
+ return headers;
16
+ }
17
+ export const openclawxAdapter = {
18
+ type: "openclawx",
19
+ async runStream(options, config, callbacks) {
20
+ const oxConfig = getOpenClawXConfig(config);
21
+ if (!oxConfig) {
22
+ throw new Error("OpenClawX adapter: missing openclawx.baseUrl in agent config");
23
+ }
24
+ const url = `${oxConfig.baseUrl}/server-api/agents/proxy-chat/stream`;
25
+ const body = { sessionId: options.sessionId, message: options.message, agentId: options.agentId };
26
+ const controller = new AbortController();
27
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
28
+ try {
29
+ const res = await fetch(url, {
30
+ method: "POST",
31
+ headers: buildHeaders(oxConfig.apiKey),
32
+ body: JSON.stringify(body),
33
+ signal: controller.signal,
34
+ });
35
+ if (!res.ok) {
36
+ const text = await res.text();
37
+ throw new Error(`OpenClawX proxy stream error ${res.status}: ${text}`);
38
+ }
39
+ const reader = res.body?.getReader();
40
+ if (!reader) {
41
+ callbacks.onDone();
42
+ return;
43
+ }
44
+ const decoder = new TextDecoder();
45
+ let buffer = "";
46
+ while (true) {
47
+ const { done, value } = await reader.read();
48
+ if (done)
49
+ break;
50
+ buffer += decoder.decode(value, { stream: true });
51
+ const lines = buffer.split("\n");
52
+ buffer = lines.pop() || "";
53
+ for (const line of lines) {
54
+ if (line.startsWith("data:")) {
55
+ const data = line.slice(5).trim();
56
+ if (!data)
57
+ continue;
58
+ try {
59
+ const parsed = JSON.parse(data);
60
+ if (parsed.done) {
61
+ callbacks.onDone();
62
+ return;
63
+ }
64
+ if (typeof parsed.delta === "string" && parsed.delta) {
65
+ callbacks.onChunk(parsed.delta);
66
+ }
67
+ }
68
+ catch {
69
+ // ignore
70
+ }
71
+ }
72
+ }
73
+ }
74
+ callbacks.onDone();
75
+ }
76
+ finally {
77
+ clearTimeout(timeoutId);
78
+ }
79
+ },
80
+ async runCollect(options, config) {
81
+ const oxConfig = getOpenClawXConfig(config);
82
+ if (!oxConfig) {
83
+ throw new Error("OpenClawX adapter: missing openclawx.baseUrl in agent config");
84
+ }
85
+ const url = `${oxConfig.baseUrl}/server-api/agents/proxy-chat`;
86
+ const body = { sessionId: options.sessionId, message: options.message, agentId: options.agentId };
87
+ const controller = new AbortController();
88
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
89
+ try {
90
+ const res = await fetch(url, {
91
+ method: "POST",
92
+ headers: buildHeaders(oxConfig.apiKey),
93
+ body: JSON.stringify(body),
94
+ signal: controller.signal,
95
+ });
96
+ if (!res.ok) {
97
+ const text = await res.text();
98
+ throw new Error(`OpenClawX proxy error ${res.status}: ${text}`);
99
+ }
100
+ const data = (await res.json());
101
+ const text = data.text;
102
+ return typeof text === "string" ? text.trim() || "(无文本回复)" : "(无文本回复)";
103
+ }
104
+ finally {
105
+ clearTimeout(timeoutId);
106
+ }
107
+ },
108
+ };
@@ -0,0 +1,3 @@
1
+ export { runForChannelStream, runForChannelCollect } from "./run-for-channel.js";
2
+ export { registerAgentProxyAdapter, getAgentProxyAdapter, listAgentProxyAdapterTypes } from "./registry.js";
3
+ export type { IAgentProxyAdapter, RunAgentForChannelOptions, RunAgentStreamCallbacks } from "./types.js";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * AgentProxy 模块:统一执行入口与适配器注册。
3
+ * 使用前需确保已通过 registerAgentProxyAdapter 注册各类型适配器;
4
+ * 本模块在加载时注册内置的 local、coze、openclawx 适配器。
5
+ */
6
+ import { registerAgentProxyAdapter } from "./registry.js";
7
+ import { localAdapter } from "./adapters/local-adapter.js";
8
+ import { cozeAdapter } from "./adapters/coze-adapter.js";
9
+ import { openclawxAdapter } from "./adapters/openclawx-adapter.js";
10
+ registerAgentProxyAdapter(localAdapter);
11
+ registerAgentProxyAdapter(cozeAdapter);
12
+ registerAgentProxyAdapter(openclawxAdapter);
13
+ export { runForChannelStream, runForChannelCollect } from "./run-for-channel.js";
14
+ export { registerAgentProxyAdapter, getAgentProxyAdapter, listAgentProxyAdapterTypes } from "./registry.js";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * AgentProxy 注册表:按 runnerType 注册与获取适配器。
3
+ */
4
+ import type { IAgentProxyAdapter } from "./types.js";
5
+ export declare function registerAgentProxyAdapter(adapter: IAgentProxyAdapter): void;
6
+ export declare function getAgentProxyAdapter(type: string): IAgentProxyAdapter | undefined;
7
+ export declare function listAgentProxyAdapterTypes(): string[];
@@ -0,0 +1,13 @@
1
+ const adapters = new Map();
2
+ export function registerAgentProxyAdapter(adapter) {
3
+ if (adapters.has(adapter.type)) {
4
+ console.warn(`[AgentProxyRegistry] overwriting adapter for type "${adapter.type}"`);
5
+ }
6
+ adapters.set(adapter.type, adapter);
7
+ }
8
+ export function getAgentProxyAdapter(type) {
9
+ return adapters.get(type);
10
+ }
11
+ export function listAgentProxyAdapterTypes() {
12
+ return Array.from(adapters.keys());
13
+ }
@@ -0,0 +1,3 @@
1
+ import type { RunAgentForChannelOptions, RunAgentStreamCallbacks } from "./types.js";
2
+ export declare function runForChannelStream(options: RunAgentForChannelOptions, callbacks: RunAgentStreamCallbacks): Promise<void>;
3
+ export declare function runForChannelCollect(options: RunAgentForChannelOptions): Promise<string>;