@simonyea/holysheep-cli 2.1.40 → 2.1.42
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/dist/configure-worker.js +4510 -0
- package/dist/index.js +9610 -0
- package/dist/process-proxy-inject.js +117 -0
- package/package.json +19 -6
- package/.gitea/workflows/sanity.yml +0 -125
- package/scripts/check-tarball-size.js +0 -44
- package/src/commands/balance.js +0 -57
- package/src/commands/claude-proxy.js +0 -248
- package/src/commands/claude.js +0 -135
- package/src/commands/doctor.js +0 -282
- package/src/commands/login.js +0 -211
- package/src/commands/openclaw.js +0 -258
- package/src/commands/reset.js +0 -53
- package/src/commands/setup.js +0 -493
- package/src/commands/upgrade.js +0 -168
- package/src/commands/webui.js +0 -622
- package/src/index.js +0 -226
- package/src/tools/aider.js +0 -78
- package/src/tools/antigravity.js +0 -42
- package/src/tools/claude-code.js +0 -228
- package/src/tools/claude-process-proxy.js +0 -1030
- package/src/tools/codex.js +0 -254
- package/src/tools/continue.js +0 -146
- package/src/tools/cursor.js +0 -71
- package/src/tools/droid.js +0 -281
- package/src/tools/env-config.js +0 -185
- package/src/tools/gemini-cli.js +0 -82
- package/src/tools/hermes.js +0 -354
- package/src/tools/index.js +0 -13
- package/src/tools/openclaw-bridge.js +0 -987
- package/src/tools/openclaw.js +0 -925
- package/src/tools/opencode.js +0 -227
- package/src/tools/process-proxy-inject.js +0 -142
- package/src/utils/config.js +0 -54
- package/src/utils/shell.js +0 -342
- package/src/utils/which.js +0 -176
- package/src/webui/aionui-runtime-fetcher.js +0 -429
- package/src/webui/aionui-runtime.js +0 -139
- package/src/webui/aionui-wrapper.js +0 -734
- package/src/webui/configure-worker.js +0 -67
- package/src/webui/server.js +0 -1572
- package/src/webui/workspace-runtime.js +0 -288
- package/src/webui/workspace-store.js +0 -325
- /package/{src/webui → dist}/index.html +0 -0
- /package/{src/tools → dist}/pty-hermes-wrapper.py +0 -0
|
@@ -0,0 +1,4510 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
5
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
6
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// src/utils/config.js
|
|
10
|
+
var require_config = __commonJS({
|
|
11
|
+
"src/utils/config.js"(exports2, module2) {
|
|
12
|
+
var fs = require("fs");
|
|
13
|
+
var path = require("path");
|
|
14
|
+
var os = require("os");
|
|
15
|
+
var CONFIG_DIR = path.join(os.homedir(), ".holysheep");
|
|
16
|
+
var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
17
|
+
var BASE_URL_ANTHROPIC = "https://api.holysheep.ai";
|
|
18
|
+
var BASE_URL_OPENAI = "https://api.holysheep.ai/v1";
|
|
19
|
+
var BASE_URL_CLAUDE_RELAY = process.env.HOLYSHEEP_CLAUDE_RELAY_URL || "https://api.holysheep.ai/claude-relay";
|
|
20
|
+
var SHOP_URL = "https://holysheep.ai";
|
|
21
|
+
function ensureDir() {
|
|
22
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
23
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
__name(ensureDir, "ensureDir");
|
|
27
|
+
function loadConfig() {
|
|
28
|
+
try {
|
|
29
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
30
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"));
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
__name(loadConfig, "loadConfig");
|
|
37
|
+
function saveConfig(data) {
|
|
38
|
+
ensureDir();
|
|
39
|
+
const current = loadConfig();
|
|
40
|
+
const merged = { ...current, ...data };
|
|
41
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2));
|
|
42
|
+
return merged;
|
|
43
|
+
}
|
|
44
|
+
__name(saveConfig, "saveConfig");
|
|
45
|
+
function getApiKey() {
|
|
46
|
+
return loadConfig().apiKey || process.env.HOLYSHEEP_API_KEY || "";
|
|
47
|
+
}
|
|
48
|
+
__name(getApiKey, "getApiKey");
|
|
49
|
+
module2.exports = {
|
|
50
|
+
CONFIG_DIR,
|
|
51
|
+
CONFIG_FILE,
|
|
52
|
+
BASE_URL_ANTHROPIC,
|
|
53
|
+
BASE_URL_OPENAI,
|
|
54
|
+
BASE_URL_CLAUDE_RELAY,
|
|
55
|
+
SHOP_URL,
|
|
56
|
+
loadConfig,
|
|
57
|
+
saveConfig,
|
|
58
|
+
getApiKey
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// src/tools/claude-process-proxy.js
|
|
64
|
+
var require_claude_process_proxy = __commonJS({
|
|
65
|
+
"src/tools/claude-process-proxy.js"(exports2, module2) {
|
|
66
|
+
"use strict";
|
|
67
|
+
var fs = require("fs");
|
|
68
|
+
var http = require("http");
|
|
69
|
+
var https = require("https");
|
|
70
|
+
var net = require("net");
|
|
71
|
+
var path = require("path");
|
|
72
|
+
var os = require("os");
|
|
73
|
+
var crypto = require("crypto");
|
|
74
|
+
var { URL: URL2 } = require("url");
|
|
75
|
+
var fetch = global.fetch || require("node-fetch");
|
|
76
|
+
var HOLYSHEEP_DIR = path.join(os.homedir(), ".holysheep");
|
|
77
|
+
var CONFIG_PATH = path.join(HOLYSHEEP_DIR, "claude-proxy.json");
|
|
78
|
+
var DEFAULT_PROXY_PORT = 14556;
|
|
79
|
+
function ensureDir() {
|
|
80
|
+
if (!fs.existsSync(HOLYSHEEP_DIR)) fs.mkdirSync(HOLYSHEEP_DIR, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
__name(ensureDir, "ensureDir");
|
|
83
|
+
function readConfig() {
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
|
|
86
|
+
} catch {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
__name(readConfig, "readConfig");
|
|
91
|
+
function writeConfig(data) {
|
|
92
|
+
ensureDir();
|
|
93
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(data, null, 2), "utf8");
|
|
94
|
+
}
|
|
95
|
+
__name(writeConfig, "writeConfig");
|
|
96
|
+
function getProcessProxyPort(config = readConfig()) {
|
|
97
|
+
const value = Number(config.processProxyPort);
|
|
98
|
+
return Number.isInteger(value) && value > 0 ? value : DEFAULT_PROXY_PORT;
|
|
99
|
+
}
|
|
100
|
+
__name(getProcessProxyPort, "getProcessProxyPort");
|
|
101
|
+
function getLocalProxyUrl(port = getProcessProxyPort()) {
|
|
102
|
+
return `http://127.0.0.1:${port}`;
|
|
103
|
+
}
|
|
104
|
+
__name(getLocalProxyUrl, "getLocalProxyUrl");
|
|
105
|
+
function getControlPlaneUrl(config) {
|
|
106
|
+
return String(config.controlPlaneUrl || config.relayUrl || "").replace(/\/+$/, "");
|
|
107
|
+
}
|
|
108
|
+
__name(getControlPlaneUrl, "getControlPlaneUrl");
|
|
109
|
+
var leaseCache = /* @__PURE__ */ new Map();
|
|
110
|
+
var MAX_PROXY_RETRIES = 3;
|
|
111
|
+
var ENABLE_TIMING_LOG = process.env.HS_CLAUDE_TIMING_LOG === "1";
|
|
112
|
+
var SLOW_PATH_LOG_MS = Number(process.env.HS_CLAUDE_SLOW_PATH_LOG_MS) || 5e3;
|
|
113
|
+
function sanitizeUrl(value) {
|
|
114
|
+
if (!value) return "";
|
|
115
|
+
try {
|
|
116
|
+
const url = new URL2(String(value));
|
|
117
|
+
return `${url.protocol}//${url.host}${url.pathname}`;
|
|
118
|
+
} catch {
|
|
119
|
+
return String(value);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
__name(sanitizeUrl, "sanitizeUrl");
|
|
123
|
+
function logProxyTiming(event, details = {}) {
|
|
124
|
+
if (!ENABLE_TIMING_LOG) return;
|
|
125
|
+
const payload = Object.fromEntries(
|
|
126
|
+
Object.entries(details).filter(([, value]) => value !== void 0 && value !== null && value !== "")
|
|
127
|
+
);
|
|
128
|
+
console.error(`[hs-claude-proxy] ${event} ${JSON.stringify(payload)}`);
|
|
129
|
+
}
|
|
130
|
+
__name(logProxyTiming, "logProxyTiming");
|
|
131
|
+
function createForwardTrace({ clientReq, targetUrl, nodeProxyUrl, sessionId, lease, attempt, isDirect }) {
|
|
132
|
+
return {
|
|
133
|
+
requestId: crypto.randomUUID().slice(0, 8),
|
|
134
|
+
sessionId,
|
|
135
|
+
nodeId: (lease == null ? void 0 : lease.nodeId) || "",
|
|
136
|
+
attempt,
|
|
137
|
+
isDirect,
|
|
138
|
+
method: (clientReq == null ? void 0 : clientReq.method) || "",
|
|
139
|
+
target: sanitizeUrl(targetUrl),
|
|
140
|
+
nodeProxy: sanitizeUrl(nodeProxyUrl),
|
|
141
|
+
leaseOpenMs: lease == null ? void 0 : lease._hsLeaseOpenMs,
|
|
142
|
+
leaseAgeMs: (lease == null ? void 0 : lease._hsLeaseOpenedAt) ? Date.now() - lease._hsLeaseOpenedAt : void 0
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
__name(createForwardTrace, "createForwardTrace");
|
|
146
|
+
function createForwardError(statusCode, body) {
|
|
147
|
+
const error = new Error(`HTTP ${statusCode}: ${String(body || "").slice(0, 200)}`);
|
|
148
|
+
error.statusCode = Number(statusCode) || 502;
|
|
149
|
+
error.body = String(body || "");
|
|
150
|
+
return error;
|
|
151
|
+
}
|
|
152
|
+
__name(createForwardError, "createForwardError");
|
|
153
|
+
function createClientValidationErrorBody(message) {
|
|
154
|
+
return JSON.stringify({
|
|
155
|
+
type: "error",
|
|
156
|
+
error: {
|
|
157
|
+
type: "client_validation_error",
|
|
158
|
+
message
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
__name(createClientValidationErrorBody, "createClientValidationErrorBody");
|
|
163
|
+
function shouldRefreshLeaseAfterError(err) {
|
|
164
|
+
const body = String((err == null ? void 0 : err.body) || (err == null ? void 0 : err.message) || "");
|
|
165
|
+
return body.includes("Claude Code \u5FC5\u987B\u4F7F\u7528 hs claude \u6307\u4EE4\u542F\u52A8") || body.includes("\u5F53\u524D\u4EE3\u7406\u8282\u70B9") || body.includes("No active Claude relay nodes are available");
|
|
166
|
+
}
|
|
167
|
+
__name(shouldRefreshLeaseAfterError, "shouldRefreshLeaseAfterError");
|
|
168
|
+
function isRetryableNodeLeaseError(err) {
|
|
169
|
+
const message = String((err == null ? void 0 : err.message) || "");
|
|
170
|
+
return [
|
|
171
|
+
"No session lease",
|
|
172
|
+
"ECONNREFUSED",
|
|
173
|
+
"ECONNRESET",
|
|
174
|
+
"socket hang up",
|
|
175
|
+
"ETIMEDOUT",
|
|
176
|
+
"EHOSTUNREACH",
|
|
177
|
+
"ENETUNREACH",
|
|
178
|
+
"Upstream proxy CONNECT failed",
|
|
179
|
+
"No available Claude accounts support the requested model",
|
|
180
|
+
"No available Claude accounts",
|
|
181
|
+
"Bad Gateway",
|
|
182
|
+
"HTTP 402",
|
|
183
|
+
"HTTP 403",
|
|
184
|
+
"HTTP 502",
|
|
185
|
+
"HTTP 503",
|
|
186
|
+
"HTTP 504",
|
|
187
|
+
"client_validation_error",
|
|
188
|
+
"upstream response timeout",
|
|
189
|
+
"upstream stream stalled",
|
|
190
|
+
"\u4E0D\u53EF\u7528"
|
|
191
|
+
].some((token) => message.includes(token));
|
|
192
|
+
}
|
|
193
|
+
__name(isRetryableNodeLeaseError, "isRetryableNodeLeaseError");
|
|
194
|
+
async function fetchFreshLease(config, sessionId, options = {}) {
|
|
195
|
+
var _a, _b;
|
|
196
|
+
const controlPlaneUrl = getControlPlaneUrl(config);
|
|
197
|
+
if (!controlPlaneUrl) throw new Error("Claude relay control plane is not configured");
|
|
198
|
+
const startedAt = Date.now();
|
|
199
|
+
const response = await fetch(`${controlPlaneUrl}/session/open`, {
|
|
200
|
+
method: "POST",
|
|
201
|
+
headers: { "content-type": "application/json" },
|
|
202
|
+
body: JSON.stringify({
|
|
203
|
+
sessionId,
|
|
204
|
+
bridgeId: config.bridgeId || "local-bridge",
|
|
205
|
+
deviceId: config.deviceId || "",
|
|
206
|
+
installSource: config.installSource || "holysheep-cli",
|
|
207
|
+
proxyMode: "claude-process",
|
|
208
|
+
forceReassign: options.forceReassign === true
|
|
209
|
+
})
|
|
210
|
+
});
|
|
211
|
+
const payload = await response.json().catch(() => null);
|
|
212
|
+
if (!response.ok || !(payload == null ? void 0 : payload.success) || !((_a = payload == null ? void 0 : payload.data) == null ? void 0 : _a.ticket)) {
|
|
213
|
+
throw new Error(((_b = payload == null ? void 0 : payload.error) == null ? void 0 : _b.message) || `Failed to open Claude session (HTTP ${response.status})`);
|
|
214
|
+
}
|
|
215
|
+
const openedAt = Date.now();
|
|
216
|
+
payload.data._hsLeaseOpenMs = openedAt - startedAt;
|
|
217
|
+
payload.data._hsLeaseOpenedAt = openedAt;
|
|
218
|
+
if (payload.data._hsLeaseOpenMs >= SLOW_PATH_LOG_MS) {
|
|
219
|
+
logProxyTiming("lease.open", {
|
|
220
|
+
sessionId,
|
|
221
|
+
nodeId: payload.data.nodeId || "",
|
|
222
|
+
leaseOpenMs: payload.data._hsLeaseOpenMs,
|
|
223
|
+
forceReassign: options.forceReassign === true
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
leaseCache.set(sessionId, payload.data);
|
|
227
|
+
return payload.data;
|
|
228
|
+
}
|
|
229
|
+
__name(fetchFreshLease, "fetchFreshLease");
|
|
230
|
+
function getCachedLease(sessionId) {
|
|
231
|
+
const cached = leaseCache.get(sessionId);
|
|
232
|
+
if (!cached) throw new Error("No session lease available");
|
|
233
|
+
return cached;
|
|
234
|
+
}
|
|
235
|
+
__name(getCachedLease, "getCachedLease");
|
|
236
|
+
function buildAuthHeaders(config, lease) {
|
|
237
|
+
return {
|
|
238
|
+
"x-hs-bridge-id": config.bridgeId || "local-bridge",
|
|
239
|
+
"x-hs-device-id": config.deviceId || "",
|
|
240
|
+
"x-hs-install-source": config.installSource || "holysheep-cli",
|
|
241
|
+
"x-hs-session-id": lease.sessionId,
|
|
242
|
+
"x-hs-bridge-ticket": lease.ticket,
|
|
243
|
+
"x-hs-node-id": lease.nodeId || ""
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
__name(buildAuthHeaders, "buildAuthHeaders");
|
|
247
|
+
function sanitizeClaudeClientHeaders(headers, config) {
|
|
248
|
+
const out = { ...headers || {} };
|
|
249
|
+
for (const k of Object.keys(out)) {
|
|
250
|
+
if (/^x-forwarded-|^x-real-ip$|^forwarded$|^via$/i.test(k)) {
|
|
251
|
+
delete out[k];
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (/^user-agent$/i.test(k) && /claude-cli|claude-code/i.test(String(out[k] || ""))) {
|
|
255
|
+
out[k] = `holysheep-cli/${config && config.cliVersion || "process-proxy"} (hs-claude-proxy)`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
260
|
+
__name(sanitizeClaudeClientHeaders, "sanitizeClaudeClientHeaders");
|
|
261
|
+
function deriveNodeProxyUrl(lease) {
|
|
262
|
+
if (process.env.HS_CLAUDE_NODE_PROXY_OVERRIDE) {
|
|
263
|
+
return String(process.env.HS_CLAUDE_NODE_PROXY_OVERRIDE).replace(/\/+$/, "");
|
|
264
|
+
}
|
|
265
|
+
if (Object.prototype.hasOwnProperty.call(lease, "nodeProxyUrl")) {
|
|
266
|
+
if (!lease.nodeProxyUrl) return null;
|
|
267
|
+
return String(lease.nodeProxyUrl);
|
|
268
|
+
}
|
|
269
|
+
if (!lease.nodeBaseUrl) return null;
|
|
270
|
+
const upstream = new URL2(String(lease.nodeBaseUrl));
|
|
271
|
+
const proxyPort = upstream.port === "3101" ? "3129" : upstream.port;
|
|
272
|
+
upstream.port = proxyPort || "3129";
|
|
273
|
+
upstream.pathname = "";
|
|
274
|
+
upstream.search = "";
|
|
275
|
+
upstream.hash = "";
|
|
276
|
+
return upstream.toString().replace(/\/+$/, "");
|
|
277
|
+
}
|
|
278
|
+
__name(deriveNodeProxyUrl, "deriveNodeProxyUrl");
|
|
279
|
+
function forwardDirectHttps({ config, lease, clientReq, clientRes, trace }) {
|
|
280
|
+
const https2 = require("https");
|
|
281
|
+
const crsBase = config.baseUrlAnthropic || "https://api.holysheep.ai";
|
|
282
|
+
const target = new URL2(clientReq.url && clientReq.url.startsWith("http") ? clientReq.url : clientReq.url, crsBase);
|
|
283
|
+
return new Promise((resolve, reject) => {
|
|
284
|
+
const sanitized = sanitizeClaudeClientHeaders(clientReq.headers, config);
|
|
285
|
+
const headers = {
|
|
286
|
+
...sanitized,
|
|
287
|
+
...buildAuthHeaders(config, lease),
|
|
288
|
+
host: target.host,
|
|
289
|
+
connection: "close"
|
|
290
|
+
};
|
|
291
|
+
const upReq = https2.request({
|
|
292
|
+
hostname: target.hostname,
|
|
293
|
+
port: target.port || 443,
|
|
294
|
+
path: target.pathname + target.search,
|
|
295
|
+
method: clientReq.method,
|
|
296
|
+
headers,
|
|
297
|
+
timeout: RESPONSE_TIMEOUT_MS + 5e3
|
|
298
|
+
}, (upRes) => {
|
|
299
|
+
clientRes.writeHead(upRes.statusCode, upRes.headers);
|
|
300
|
+
upRes.pipe(clientRes);
|
|
301
|
+
upRes.on("end", resolve);
|
|
302
|
+
upRes.on("error", reject);
|
|
303
|
+
});
|
|
304
|
+
upReq.on("error", reject);
|
|
305
|
+
upReq.on("timeout", () => {
|
|
306
|
+
upReq.destroy(new Error("direct-https upstream timeout"));
|
|
307
|
+
});
|
|
308
|
+
clientReq.pipe(upReq);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
__name(forwardDirectHttps, "forwardDirectHttps");
|
|
312
|
+
function forwardViaNodeProxy({ nodeProxyUrl, targetUrl, clientReq, clientRes, extraHeaders = {}, trace = null }) {
|
|
313
|
+
const upstream = new URL2(nodeProxyUrl);
|
|
314
|
+
return new Promise((resolve, reject) => {
|
|
315
|
+
const requestStartedAt = Date.now();
|
|
316
|
+
const resolvedTrace = trace || createForwardTrace({
|
|
317
|
+
clientReq,
|
|
318
|
+
targetUrl,
|
|
319
|
+
nodeProxyUrl,
|
|
320
|
+
sessionId: clientReq.headers["x-hs-session-id"] || "",
|
|
321
|
+
lease: null,
|
|
322
|
+
attempt: 0,
|
|
323
|
+
isDirect: !String(clientReq.url || "").startsWith("http")
|
|
324
|
+
});
|
|
325
|
+
let settled = false;
|
|
326
|
+
let responseTimer = null;
|
|
327
|
+
let stallTimer = null;
|
|
328
|
+
let sawUpstreamResponse = false;
|
|
329
|
+
let sawUpstreamData = false;
|
|
330
|
+
let forwardReq = null;
|
|
331
|
+
let upstreamAssignedAt = null;
|
|
332
|
+
let firstByteAt = null;
|
|
333
|
+
const clearResponseTimer = /* @__PURE__ */ __name(() => {
|
|
334
|
+
if (responseTimer) {
|
|
335
|
+
clearTimeout(responseTimer);
|
|
336
|
+
responseTimer = null;
|
|
337
|
+
}
|
|
338
|
+
}, "clearResponseTimer");
|
|
339
|
+
const clearStallTimer = /* @__PURE__ */ __name(() => {
|
|
340
|
+
if (stallTimer) {
|
|
341
|
+
clearTimeout(stallTimer);
|
|
342
|
+
stallTimer = null;
|
|
343
|
+
}
|
|
344
|
+
}, "clearStallTimer");
|
|
345
|
+
const clearTimers = /* @__PURE__ */ __name(() => {
|
|
346
|
+
clearResponseTimer();
|
|
347
|
+
clearStallTimer();
|
|
348
|
+
}, "clearTimers");
|
|
349
|
+
const finish = /* @__PURE__ */ __name(() => {
|
|
350
|
+
if (settled) return;
|
|
351
|
+
settled = true;
|
|
352
|
+
clearTimers();
|
|
353
|
+
const finishedAt = Date.now();
|
|
354
|
+
const totalMs = finishedAt - requestStartedAt;
|
|
355
|
+
if (totalMs >= SLOW_PATH_LOG_MS) {
|
|
356
|
+
logProxyTiming("request.complete", {
|
|
357
|
+
...resolvedTrace,
|
|
358
|
+
totalMs,
|
|
359
|
+
connectMs: upstreamAssignedAt ? upstreamAssignedAt - requestStartedAt : void 0,
|
|
360
|
+
firstByteMs: firstByteAt ? firstByteAt - requestStartedAt : void 0,
|
|
361
|
+
streamMs: firstByteAt ? finishedAt - firstByteAt : void 0,
|
|
362
|
+
bytesStarted: sawUpstreamData
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
resolve();
|
|
366
|
+
}, "finish");
|
|
367
|
+
const failWithAnthropicError = /* @__PURE__ */ __name((message) => {
|
|
368
|
+
if (settled) return;
|
|
369
|
+
settled = true;
|
|
370
|
+
clearTimers();
|
|
371
|
+
const failedAt = Date.now();
|
|
372
|
+
logProxyTiming("request.fail", {
|
|
373
|
+
...resolvedTrace,
|
|
374
|
+
totalMs: failedAt - requestStartedAt,
|
|
375
|
+
connectMs: upstreamAssignedAt ? upstreamAssignedAt - requestStartedAt : void 0,
|
|
376
|
+
firstByteMs: firstByteAt ? firstByteAt - requestStartedAt : void 0,
|
|
377
|
+
bytesStarted: sawUpstreamData,
|
|
378
|
+
error: message
|
|
379
|
+
});
|
|
380
|
+
if (forwardReq && !forwardReq.destroyed) {
|
|
381
|
+
forwardReq.destroy();
|
|
382
|
+
}
|
|
383
|
+
const error = createForwardError(400, createClientValidationErrorBody(message));
|
|
384
|
+
error.message = message;
|
|
385
|
+
reject(error);
|
|
386
|
+
}, "failWithAnthropicError");
|
|
387
|
+
const fail = /* @__PURE__ */ __name((error) => {
|
|
388
|
+
const err = error instanceof Error ? error : new Error(String(error || "Proxy error"));
|
|
389
|
+
return failWithAnthropicError(err.message || "Proxy error");
|
|
390
|
+
}, "fail");
|
|
391
|
+
const armResponseTimer = /* @__PURE__ */ __name(() => {
|
|
392
|
+
if (settled || sawUpstreamResponse || responseTimer) return;
|
|
393
|
+
responseTimer = setTimeout(() => failWithAnthropicError("upstream response timeout"), RESPONSE_TIMEOUT_MS);
|
|
394
|
+
}, "armResponseTimer");
|
|
395
|
+
const armStallTimer = /* @__PURE__ */ __name(() => {
|
|
396
|
+
if (settled) return;
|
|
397
|
+
sawUpstreamData = true;
|
|
398
|
+
clearStallTimer();
|
|
399
|
+
stallTimer = setTimeout(() => failWithAnthropicError("upstream stream stalled"), STALL_TIMEOUT_MS);
|
|
400
|
+
}, "armStallTimer");
|
|
401
|
+
const finalHeaders = {
|
|
402
|
+
...sanitizeClaudeClientHeaders(clientReq.headers, { cliVersion: void 0 }),
|
|
403
|
+
...extraHeaders,
|
|
404
|
+
host: targetUrl.host,
|
|
405
|
+
connection: "close"
|
|
406
|
+
};
|
|
407
|
+
const forwardPath = (() => {
|
|
408
|
+
const clone = new URL2(targetUrl.toString());
|
|
409
|
+
clone.protocol = "http:";
|
|
410
|
+
return clone.toString();
|
|
411
|
+
})();
|
|
412
|
+
if (ENABLE_TIMING_LOG) {
|
|
413
|
+
const hsHeaders = Object.fromEntries(
|
|
414
|
+
Object.entries(finalHeaders).filter(([k]) => /^x-hs-/i.test(k))
|
|
415
|
+
);
|
|
416
|
+
console.error(
|
|
417
|
+
`[hs-claude-proxy] forward.headers ${JSON.stringify({
|
|
418
|
+
target: sanitizeUrl(targetUrl),
|
|
419
|
+
forwardPath,
|
|
420
|
+
node: sanitizeUrl(nodeProxyUrl),
|
|
421
|
+
hsHeaders,
|
|
422
|
+
clientHeadersKeys: Object.keys(clientReq.headers).slice(0, 20)
|
|
423
|
+
})}`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
forwardReq = http.request({
|
|
427
|
+
host: upstream.hostname,
|
|
428
|
+
port: Number(upstream.port || 80),
|
|
429
|
+
method: clientReq.method,
|
|
430
|
+
path: forwardPath,
|
|
431
|
+
agent: false,
|
|
432
|
+
headers: finalHeaders
|
|
433
|
+
}, (forwardRes) => {
|
|
434
|
+
sawUpstreamResponse = true;
|
|
435
|
+
clearResponseTimer();
|
|
436
|
+
const status = forwardRes.statusCode || 502;
|
|
437
|
+
if (status === 403 || status === 502 || status === 503) {
|
|
438
|
+
const chunks = [];
|
|
439
|
+
forwardRes.on("data", (c) => chunks.push(c));
|
|
440
|
+
forwardRes.on("end", () => {
|
|
441
|
+
if (settled) return;
|
|
442
|
+
settled = true;
|
|
443
|
+
clearTimers();
|
|
444
|
+
const body = Buffer.concat(chunks).toString("utf8");
|
|
445
|
+
const error = createForwardError(status, body);
|
|
446
|
+
logProxyTiming("request.upstream_error", {
|
|
447
|
+
...resolvedTrace,
|
|
448
|
+
status,
|
|
449
|
+
totalMs: Date.now() - requestStartedAt,
|
|
450
|
+
body: String(body || "").slice(0, 200)
|
|
451
|
+
});
|
|
452
|
+
reject(error);
|
|
453
|
+
});
|
|
454
|
+
forwardRes.on("error", fail);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
upstreamAssignedAt = upstreamAssignedAt || Date.now();
|
|
458
|
+
clientRes.writeHead(status, forwardRes.headers);
|
|
459
|
+
forwardRes.on("data", () => {
|
|
460
|
+
if (!firstByteAt) {
|
|
461
|
+
firstByteAt = Date.now();
|
|
462
|
+
logProxyTiming("request.first_byte", {
|
|
463
|
+
...resolvedTrace,
|
|
464
|
+
firstByteMs: firstByteAt - requestStartedAt
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
armStallTimer();
|
|
468
|
+
});
|
|
469
|
+
forwardRes.once("end", () => {
|
|
470
|
+
clearStallTimer();
|
|
471
|
+
finish();
|
|
472
|
+
});
|
|
473
|
+
forwardRes.once("close", () => {
|
|
474
|
+
clearStallTimer();
|
|
475
|
+
finish();
|
|
476
|
+
});
|
|
477
|
+
forwardRes.once("error", fail);
|
|
478
|
+
forwardRes.pipe(clientRes);
|
|
479
|
+
});
|
|
480
|
+
forwardReq.on("socket", (socket) => {
|
|
481
|
+
upstreamAssignedAt = upstreamAssignedAt || Date.now();
|
|
482
|
+
socket.once("connect", () => {
|
|
483
|
+
upstreamAssignedAt = upstreamAssignedAt || Date.now();
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
forwardReq.setTimeout(RESPONSE_TIMEOUT_MS, () => {
|
|
487
|
+
if (!sawUpstreamResponse) failWithAnthropicError("upstream response timeout");
|
|
488
|
+
});
|
|
489
|
+
forwardReq.once("error", fail);
|
|
490
|
+
clientReq.once("aborted", () => finish());
|
|
491
|
+
armResponseTimer();
|
|
492
|
+
clientReq.pipe(forwardReq);
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
__name(forwardViaNodeProxy, "forwardViaNodeProxy");
|
|
496
|
+
function createConnectTunnel(proxyUrl, target, headers) {
|
|
497
|
+
return new Promise((resolve, reject) => {
|
|
498
|
+
const upstream = new URL2(proxyUrl);
|
|
499
|
+
const request = http.request({
|
|
500
|
+
host: upstream.hostname,
|
|
501
|
+
port: Number(upstream.port || 80),
|
|
502
|
+
method: "CONNECT",
|
|
503
|
+
path: target,
|
|
504
|
+
headers,
|
|
505
|
+
agent: false,
|
|
506
|
+
timeout: 15e3
|
|
507
|
+
// CONNECT 握手 15 秒超时
|
|
508
|
+
});
|
|
509
|
+
request.once("connect", (response, socket, head) => {
|
|
510
|
+
if (response.statusCode !== 200) {
|
|
511
|
+
socket.destroy();
|
|
512
|
+
return reject(new Error(`Upstream proxy CONNECT failed (HTTP ${response.statusCode})`));
|
|
513
|
+
}
|
|
514
|
+
if (head == null ? void 0 : head.length) socket.unshift(head);
|
|
515
|
+
resolve(socket);
|
|
516
|
+
});
|
|
517
|
+
request.once("timeout", () => {
|
|
518
|
+
request.destroy(new Error("CONNECT tunnel handshake timeout"));
|
|
519
|
+
});
|
|
520
|
+
request.once("error", reject);
|
|
521
|
+
request.end();
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
__name(createConnectTunnel, "createConnectTunnel");
|
|
525
|
+
var RESPONSE_TIMEOUT_MS = Number(process.env.HS_CLAUDE_RESPONSE_TIMEOUT_MS) || 3e4;
|
|
526
|
+
var STALL_TIMEOUT_MS = Number(process.env.HS_CLAUDE_STALL_TIMEOUT_MS) || 12e4;
|
|
527
|
+
function pipeWithCleanup(a, b) {
|
|
528
|
+
for (const sock of [a, b]) {
|
|
529
|
+
if (typeof sock.setKeepAlive === "function") sock.setKeepAlive(true, 1e4);
|
|
530
|
+
}
|
|
531
|
+
let timer = null;
|
|
532
|
+
let closed = false;
|
|
533
|
+
let streaming = false;
|
|
534
|
+
const clearTimer = /* @__PURE__ */ __name(() => {
|
|
535
|
+
if (timer) {
|
|
536
|
+
clearTimeout(timer);
|
|
537
|
+
timer = null;
|
|
538
|
+
}
|
|
539
|
+
}, "clearTimer");
|
|
540
|
+
const destroySocket = /* @__PURE__ */ __name((sock, err) => {
|
|
541
|
+
if (!sock || sock.destroyed) return;
|
|
542
|
+
if (err) sock.destroy(err);
|
|
543
|
+
else sock.destroy();
|
|
544
|
+
}, "destroySocket");
|
|
545
|
+
const close = /* @__PURE__ */ __name((reason) => {
|
|
546
|
+
if (closed) return;
|
|
547
|
+
closed = true;
|
|
548
|
+
clearTimer();
|
|
549
|
+
const err = reason ? new Error(reason) : null;
|
|
550
|
+
destroySocket(a, err);
|
|
551
|
+
destroySocket(b, err);
|
|
552
|
+
}, "close");
|
|
553
|
+
const armResponseTimer = /* @__PURE__ */ __name(() => {
|
|
554
|
+
if (streaming || closed || timer) return;
|
|
555
|
+
timer = setTimeout(() => close("upstream response timeout"), RESPONSE_TIMEOUT_MS);
|
|
556
|
+
}, "armResponseTimer");
|
|
557
|
+
const armStallTimer = /* @__PURE__ */ __name(() => {
|
|
558
|
+
if (closed) return;
|
|
559
|
+
streaming = true;
|
|
560
|
+
clearTimer();
|
|
561
|
+
timer = setTimeout(() => close("upstream stream stalled"), STALL_TIMEOUT_MS);
|
|
562
|
+
}, "armStallTimer");
|
|
563
|
+
b.on("data", armStallTimer);
|
|
564
|
+
armResponseTimer();
|
|
565
|
+
a.pipe(b);
|
|
566
|
+
b.pipe(a);
|
|
567
|
+
a.once("error", () => close());
|
|
568
|
+
b.once("error", () => close());
|
|
569
|
+
a.once("close", () => close());
|
|
570
|
+
b.once("close", () => close());
|
|
571
|
+
}
|
|
572
|
+
__name(pipeWithCleanup, "pipeWithCleanup");
|
|
573
|
+
function createProcessProxyServer({ sessionId, configPath = CONFIG_PATH, allowAnthropicConnect = false }) {
|
|
574
|
+
const server = http.createServer(async (clientReq, clientRes) => {
|
|
575
|
+
const isDirect = !clientReq.url.startsWith("http");
|
|
576
|
+
if (ENABLE_TIMING_LOG) {
|
|
577
|
+
console.error(
|
|
578
|
+
`[hs-claude-proxy] incoming ${JSON.stringify({
|
|
579
|
+
method: clientReq.method,
|
|
580
|
+
url: String(clientReq.url).slice(0, 120),
|
|
581
|
+
ua: String(clientReq.headers["user-agent"] || "").slice(0, 60),
|
|
582
|
+
host: clientReq.headers.host || "",
|
|
583
|
+
isDirect
|
|
584
|
+
})}`
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
let bufferedBody = null;
|
|
588
|
+
if (clientReq.method && !["GET", "HEAD", "OPTIONS"].includes(clientReq.method.toUpperCase())) {
|
|
589
|
+
try {
|
|
590
|
+
const chunks = [];
|
|
591
|
+
for await (const chunk of clientReq) chunks.push(chunk);
|
|
592
|
+
bufferedBody = Buffer.concat(chunks);
|
|
593
|
+
} catch (e) {
|
|
594
|
+
if (!clientRes.headersSent) {
|
|
595
|
+
clientRes.writeHead(400, { "content-type": "text/plain" });
|
|
596
|
+
clientRes.end("client disconnected while uploading body: " + (e && e.message));
|
|
597
|
+
}
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const { Readable } = require("stream");
|
|
602
|
+
const makeBodyStream = /* @__PURE__ */ __name(() => {
|
|
603
|
+
if (bufferedBody === null) return null;
|
|
604
|
+
const r = new Readable({ read() {
|
|
605
|
+
} });
|
|
606
|
+
r.push(bufferedBody);
|
|
607
|
+
r.push(null);
|
|
608
|
+
return r;
|
|
609
|
+
}, "makeBodyStream");
|
|
610
|
+
const originalClientReq = clientReq;
|
|
611
|
+
clientReq = new Proxy(originalClientReq, {
|
|
612
|
+
get(target, prop, receiver) {
|
|
613
|
+
if (prop === "pipe") {
|
|
614
|
+
return (dest, opts) => {
|
|
615
|
+
const s = makeBodyStream();
|
|
616
|
+
if (!s) {
|
|
617
|
+
if (dest && typeof dest.end === "function") dest.end();
|
|
618
|
+
return dest;
|
|
619
|
+
}
|
|
620
|
+
return s.pipe(dest, opts);
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
return Reflect.get(target, prop, receiver);
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
const doForward = /* @__PURE__ */ __name(async (lease, attempt) => {
|
|
627
|
+
const config = readConfig(configPath);
|
|
628
|
+
const nodeProxyUrl = deriveNodeProxyUrl(lease);
|
|
629
|
+
if (nodeProxyUrl === null) {
|
|
630
|
+
logProxyTiming("request.direct-noproxy", { sessionId, nodeId: lease.nodeId || "", attempt });
|
|
631
|
+
return forwardDirectHttps({ config, lease, clientReq, clientRes, trace: null });
|
|
632
|
+
}
|
|
633
|
+
if (isDirect) {
|
|
634
|
+
const crsBase = config.baseUrlAnthropic || "https://api.holysheep.ai";
|
|
635
|
+
const target = new URL2(clientReq.url, crsBase);
|
|
636
|
+
return forwardViaNodeProxy({
|
|
637
|
+
nodeProxyUrl,
|
|
638
|
+
targetUrl: target,
|
|
639
|
+
clientReq,
|
|
640
|
+
clientRes,
|
|
641
|
+
extraHeaders: buildAuthHeaders(config, lease),
|
|
642
|
+
trace: createForwardTrace({
|
|
643
|
+
clientReq,
|
|
644
|
+
targetUrl: target,
|
|
645
|
+
nodeProxyUrl,
|
|
646
|
+
sessionId,
|
|
647
|
+
lease,
|
|
648
|
+
attempt,
|
|
649
|
+
isDirect
|
|
650
|
+
})
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
const targetUrl = new URL2(clientReq.url);
|
|
654
|
+
const headers = {
|
|
655
|
+
...buildAuthHeaders(config, lease),
|
|
656
|
+
host: targetUrl.host
|
|
657
|
+
};
|
|
658
|
+
return forwardViaNodeProxy({
|
|
659
|
+
nodeProxyUrl,
|
|
660
|
+
targetUrl,
|
|
661
|
+
clientReq,
|
|
662
|
+
clientRes,
|
|
663
|
+
extraHeaders: headers,
|
|
664
|
+
trace: createForwardTrace({
|
|
665
|
+
clientReq,
|
|
666
|
+
targetUrl,
|
|
667
|
+
nodeProxyUrl,
|
|
668
|
+
sessionId,
|
|
669
|
+
lease,
|
|
670
|
+
attempt,
|
|
671
|
+
isDirect
|
|
672
|
+
})
|
|
673
|
+
});
|
|
674
|
+
}, "doForward");
|
|
675
|
+
let lastError = null;
|
|
676
|
+
for (let attempt = 0; attempt <= MAX_PROXY_RETRIES; attempt++) {
|
|
677
|
+
try {
|
|
678
|
+
if (attempt === 0) {
|
|
679
|
+
await doForward(getCachedLease(sessionId), attempt);
|
|
680
|
+
} else {
|
|
681
|
+
const config = readConfig(configPath);
|
|
682
|
+
const prevLease = (() => {
|
|
683
|
+
try {
|
|
684
|
+
return leaseCache.get(sessionId);
|
|
685
|
+
} catch {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
})();
|
|
689
|
+
const forceReassign = shouldRefreshLeaseAfterError(lastError);
|
|
690
|
+
const retryReason = String((lastError == null ? void 0 : lastError.body) || (lastError == null ? void 0 : lastError.message) || "").slice(0, 120);
|
|
691
|
+
if (forceReassign) {
|
|
692
|
+
console.error(`[hs-claude-proxy] lease.force-reassign ${JSON.stringify({
|
|
693
|
+
sessionId,
|
|
694
|
+
nodeId: (prevLease == null ? void 0 : prevLease.nodeId) || "",
|
|
695
|
+
attempt,
|
|
696
|
+
reason: retryReason
|
|
697
|
+
})}`);
|
|
698
|
+
} else {
|
|
699
|
+
console.error(`[hs-claude-proxy] lease.sticky-hit ${JSON.stringify({
|
|
700
|
+
sessionId,
|
|
701
|
+
nodeId: (prevLease == null ? void 0 : prevLease.nodeId) || "",
|
|
702
|
+
attempt,
|
|
703
|
+
retryReason
|
|
704
|
+
})}`);
|
|
705
|
+
}
|
|
706
|
+
leaseCache.delete(sessionId);
|
|
707
|
+
if (forceReassign) {
|
|
708
|
+
await closeSession(configPath, sessionId);
|
|
709
|
+
}
|
|
710
|
+
const freshLease = await fetchFreshLease(config, sessionId, {
|
|
711
|
+
forceReassign
|
|
712
|
+
});
|
|
713
|
+
await doForward(freshLease, attempt);
|
|
714
|
+
}
|
|
715
|
+
lastError = null;
|
|
716
|
+
break;
|
|
717
|
+
} catch (err) {
|
|
718
|
+
lastError = err;
|
|
719
|
+
logProxyTiming("request.retry", {
|
|
720
|
+
sessionId,
|
|
721
|
+
attempt,
|
|
722
|
+
error: String((err == null ? void 0 : err.message) || err),
|
|
723
|
+
retryable: isRetryableNodeLeaseError(err),
|
|
724
|
+
forceReassign: shouldRefreshLeaseAfterError(err)
|
|
725
|
+
});
|
|
726
|
+
if (clientRes.headersSent) return;
|
|
727
|
+
if (!isRetryableNodeLeaseError(err) && attempt > 0) break;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
if (lastError && !clientRes.headersSent) {
|
|
731
|
+
const msg = String((lastError == null ? void 0 : lastError.message) || (lastError == null ? void 0 : lastError.body) || "");
|
|
732
|
+
const isConnectionLevel = /ECONNREFUSED|EHOSTUNREACH|ETIMEDOUT|ENETUNREACH|connect/i.test(msg);
|
|
733
|
+
if (isConnectionLevel) {
|
|
734
|
+
try {
|
|
735
|
+
const config = readConfig(configPath);
|
|
736
|
+
const lease = getCachedLease(sessionId) || await fetchFreshLease(config, sessionId, {});
|
|
737
|
+
logProxyTiming("request.direct-fallback", {
|
|
738
|
+
sessionId,
|
|
739
|
+
originalError: msg.slice(0, 160)
|
|
740
|
+
});
|
|
741
|
+
await forwardDirectHttps({ config, lease, clientReq, clientRes, trace: null });
|
|
742
|
+
lastError = null;
|
|
743
|
+
} catch (fallbackErr) {
|
|
744
|
+
lastError = fallbackErr;
|
|
745
|
+
logProxyTiming("request.direct-fallback.fail", {
|
|
746
|
+
sessionId,
|
|
747
|
+
error: String((fallbackErr == null ? void 0 : fallbackErr.message) || fallbackErr)
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
if (lastError && !clientRes.headersSent) {
|
|
753
|
+
const status = Number(lastError.statusCode || 502);
|
|
754
|
+
const body = String(lastError.body || lastError.message || "Proxy error");
|
|
755
|
+
const isJson = body.trim().startsWith("{");
|
|
756
|
+
clientRes.writeHead(status, {
|
|
757
|
+
"content-type": isJson ? "application/json; charset=utf-8" : "text/plain; charset=utf-8"
|
|
758
|
+
});
|
|
759
|
+
clientRes.end(body);
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
const BLOCKED_CONNECT = allowAnthropicConnect ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set(["api.anthropic.com"]);
|
|
763
|
+
server.on("connect", async (req, clientSocket, head) => {
|
|
764
|
+
const target = String(req.url || "").trim();
|
|
765
|
+
if (ENABLE_TIMING_LOG) {
|
|
766
|
+
console.error(
|
|
767
|
+
`[hs-claude-proxy] incoming.CONNECT ${JSON.stringify({
|
|
768
|
+
target: target.slice(0, 120),
|
|
769
|
+
ua: String(req.headers["user-agent"] || "").slice(0, 60),
|
|
770
|
+
headers: Object.keys(req.headers).slice(0, 20)
|
|
771
|
+
})}`
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
const [host, rawPort] = target.split(":");
|
|
775
|
+
const port = Number(rawPort || 443);
|
|
776
|
+
if (!host || !Number.isInteger(port) || ![80, 443].includes(port)) {
|
|
777
|
+
clientSocket.write("HTTP/1.1 403 Forbidden\r\n\r\n");
|
|
778
|
+
return clientSocket.destroy();
|
|
779
|
+
}
|
|
780
|
+
if (BLOCKED_CONNECT.has(host)) {
|
|
781
|
+
clientSocket.write("HTTP/1.1 403 Forbidden\r\ncontent-type: text/plain; charset=utf-8\r\n\r\nDirect tunnel to api.anthropic.com is blocked; use ANTHROPIC_BASE_URL path");
|
|
782
|
+
return clientSocket.destroy();
|
|
783
|
+
}
|
|
784
|
+
const doConnect = /* @__PURE__ */ __name(async (lease) => {
|
|
785
|
+
const nodeProxyUrl = deriveNodeProxyUrl(lease);
|
|
786
|
+
if (nodeProxyUrl === null) {
|
|
787
|
+
logProxyTiming("connect.direct-noproxy", { sessionId, nodeId: lease.nodeId || "", target });
|
|
788
|
+
const upstreamSocket2 = await new Promise((resolve, reject) => {
|
|
789
|
+
const sock = net.connect(port, host, () => resolve(sock));
|
|
790
|
+
sock.once("error", reject);
|
|
791
|
+
sock.setTimeout(15e3, () => sock.destroy(new Error("CONNECT direct tunnel timeout")));
|
|
792
|
+
});
|
|
793
|
+
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
|
794
|
+
if (head == null ? void 0 : head.length) upstreamSocket2.write(head);
|
|
795
|
+
pipeWithCleanup(clientSocket, upstreamSocket2);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
const upstreamSocket = await createConnectTunnel(
|
|
799
|
+
nodeProxyUrl,
|
|
800
|
+
target,
|
|
801
|
+
buildAuthHeaders(readConfig(configPath), lease)
|
|
802
|
+
);
|
|
803
|
+
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
|
804
|
+
if (head == null ? void 0 : head.length) upstreamSocket.write(head);
|
|
805
|
+
pipeWithCleanup(clientSocket, upstreamSocket);
|
|
806
|
+
}, "doConnect");
|
|
807
|
+
let lastError = null;
|
|
808
|
+
for (let attempt = 0; attempt <= MAX_PROXY_RETRIES; attempt++) {
|
|
809
|
+
try {
|
|
810
|
+
if (attempt === 0) {
|
|
811
|
+
await doConnect(getCachedLease(sessionId));
|
|
812
|
+
} else {
|
|
813
|
+
const config = readConfig(configPath);
|
|
814
|
+
const prevLease = (() => {
|
|
815
|
+
try {
|
|
816
|
+
return leaseCache.get(sessionId);
|
|
817
|
+
} catch {
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
})();
|
|
821
|
+
const forceReassign = shouldRefreshLeaseAfterError(lastError);
|
|
822
|
+
const retryReason = String((lastError == null ? void 0 : lastError.body) || (lastError == null ? void 0 : lastError.message) || "").slice(0, 120);
|
|
823
|
+
if (forceReassign) {
|
|
824
|
+
console.error(`[hs-claude-proxy] lease.force-reassign ${JSON.stringify({
|
|
825
|
+
sessionId,
|
|
826
|
+
nodeId: (prevLease == null ? void 0 : prevLease.nodeId) || "",
|
|
827
|
+
attempt,
|
|
828
|
+
reason: retryReason,
|
|
829
|
+
path: "CONNECT"
|
|
830
|
+
})}`);
|
|
831
|
+
} else {
|
|
832
|
+
console.error(`[hs-claude-proxy] lease.sticky-hit ${JSON.stringify({
|
|
833
|
+
sessionId,
|
|
834
|
+
nodeId: (prevLease == null ? void 0 : prevLease.nodeId) || "",
|
|
835
|
+
attempt,
|
|
836
|
+
retryReason,
|
|
837
|
+
path: "CONNECT"
|
|
838
|
+
})}`);
|
|
839
|
+
}
|
|
840
|
+
leaseCache.delete(sessionId);
|
|
841
|
+
if (forceReassign) {
|
|
842
|
+
await closeSession(configPath, sessionId);
|
|
843
|
+
}
|
|
844
|
+
const freshLease = await fetchFreshLease(config, sessionId, {
|
|
845
|
+
forceReassign
|
|
846
|
+
});
|
|
847
|
+
await doConnect(freshLease);
|
|
848
|
+
}
|
|
849
|
+
lastError = null;
|
|
850
|
+
break;
|
|
851
|
+
} catch (err) {
|
|
852
|
+
lastError = err;
|
|
853
|
+
if (!isRetryableNodeLeaseError(err) && attempt > 0) break;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (lastError) {
|
|
857
|
+
const status = Number(lastError.statusCode || 502);
|
|
858
|
+
const body = String(lastError.body || lastError.message || "Proxy error");
|
|
859
|
+
const statusText = status === 403 ? "Forbidden" : status === 503 ? "Service Unavailable" : "Bad Gateway";
|
|
860
|
+
const contentType = body.trim().startsWith("{") ? "application/json; charset=utf-8" : "text/plain; charset=utf-8";
|
|
861
|
+
clientSocket.write(`HTTP/1.1 ${status} ${statusText}\r
|
|
862
|
+
content-type: ${contentType}\r
|
|
863
|
+
\r
|
|
864
|
+
${body}`);
|
|
865
|
+
clientSocket.destroy();
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
return server;
|
|
869
|
+
}
|
|
870
|
+
__name(createProcessProxyServer, "createProcessProxyServer");
|
|
871
|
+
async function startProcessProxy({ port = null, sessionId = null, configPath = CONFIG_PATH, allowAnthropicConnect = false } = {}) {
|
|
872
|
+
const config = readConfig(configPath);
|
|
873
|
+
const preferredPort = port || getProcessProxyPort(config);
|
|
874
|
+
const effectiveSessionId = sessionId || crypto.randomUUID();
|
|
875
|
+
await fetchFreshLease(config, effectiveSessionId);
|
|
876
|
+
const server = createProcessProxyServer({ sessionId: effectiveSessionId, configPath, allowAnthropicConnect });
|
|
877
|
+
return new Promise((resolve, reject) => {
|
|
878
|
+
const tryListen = /* @__PURE__ */ __name((p) => {
|
|
879
|
+
server.once("error", (err) => {
|
|
880
|
+
if (err.code === "EADDRINUSE") {
|
|
881
|
+
server.once("error", reject);
|
|
882
|
+
server.listen(0, "127.0.0.1");
|
|
883
|
+
} else {
|
|
884
|
+
reject(err);
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
server.once("listening", () => {
|
|
888
|
+
resolve({ server, port: server.address().port, sessionId: effectiveSessionId });
|
|
889
|
+
});
|
|
890
|
+
server.listen(p, "127.0.0.1");
|
|
891
|
+
}, "tryListen");
|
|
892
|
+
tryListen(preferredPort);
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
__name(startProcessProxy, "startProcessProxy");
|
|
896
|
+
async function closeSession(configPath, sessionId) {
|
|
897
|
+
if (!sessionId) return;
|
|
898
|
+
const config = readConfig(configPath);
|
|
899
|
+
const controlPlaneUrl = getControlPlaneUrl(config);
|
|
900
|
+
if (!controlPlaneUrl) {
|
|
901
|
+
logProxyTiming("lease.close", { sessionId, skipped: "no-control-plane" });
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
const startedAt = Date.now();
|
|
905
|
+
let status = "unknown";
|
|
906
|
+
try {
|
|
907
|
+
const response = await fetch(`${controlPlaneUrl}/session/close`, {
|
|
908
|
+
method: "POST",
|
|
909
|
+
headers: { "content-type": "application/json" },
|
|
910
|
+
body: JSON.stringify({ sessionId })
|
|
911
|
+
});
|
|
912
|
+
status = response.ok ? "ok" : `http_${response.status}`;
|
|
913
|
+
} catch (err) {
|
|
914
|
+
status = `err_${err.name || "unknown"}`;
|
|
915
|
+
}
|
|
916
|
+
console.error(
|
|
917
|
+
`[hs-claude-proxy] lease.close ${JSON.stringify({
|
|
918
|
+
sessionId,
|
|
919
|
+
status,
|
|
920
|
+
durationMs: Date.now() - startedAt
|
|
921
|
+
})}`
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
__name(closeSession, "closeSession");
|
|
925
|
+
module2.exports = {
|
|
926
|
+
CONFIG_PATH,
|
|
927
|
+
DEFAULT_PROXY_PORT,
|
|
928
|
+
closeSession,
|
|
929
|
+
getLocalProxyUrl,
|
|
930
|
+
getProcessProxyPort,
|
|
931
|
+
getControlPlaneUrl,
|
|
932
|
+
readConfig,
|
|
933
|
+
startProcessProxy,
|
|
934
|
+
writeConfig
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
// src/utils/which.js
|
|
940
|
+
var require_which = __commonJS({
|
|
941
|
+
"src/utils/which.js"(exports2, module2) {
|
|
942
|
+
var path = require("path");
|
|
943
|
+
var fs = require("fs");
|
|
944
|
+
var { exec, execSync } = require("child_process");
|
|
945
|
+
function canRun(command, options = {}) {
|
|
946
|
+
try {
|
|
947
|
+
execSync(command, { stdio: "ignore", ...options });
|
|
948
|
+
return true;
|
|
949
|
+
} catch {
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
__name(canRun, "canRun");
|
|
954
|
+
function canRunAsync(command, options = {}) {
|
|
955
|
+
return new Promise((resolve) => {
|
|
956
|
+
exec(command, { timeout: 3e3, windowsHide: true, ...options }, (error) => {
|
|
957
|
+
resolve(!error);
|
|
958
|
+
});
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
__name(canRunAsync, "canRunAsync");
|
|
962
|
+
function getWindowsKnownPaths(cmd) {
|
|
963
|
+
if (process.platform !== "win32") return [];
|
|
964
|
+
const home = process.env.USERPROFILE || process.env.HOME || "";
|
|
965
|
+
const appData = process.env.APPDATA || "";
|
|
966
|
+
const localAppData = process.env.LOCALAPPDATA || "";
|
|
967
|
+
const paths = [];
|
|
968
|
+
if (home) paths.push(path.join(home, ".local", "bin", `${cmd}.exe`));
|
|
969
|
+
if (appData) {
|
|
970
|
+
paths.push(path.join(appData, "npm", `${cmd}.cmd`));
|
|
971
|
+
paths.push(path.join(appData, "npm", `${cmd}.exe`));
|
|
972
|
+
}
|
|
973
|
+
if (localAppData) {
|
|
974
|
+
paths.push(path.join(localAppData, "Programs", cmd, `${cmd}.exe`));
|
|
975
|
+
paths.push(path.join(localAppData, cmd, `${cmd}.exe`));
|
|
976
|
+
paths.push(path.join(localAppData, "factory", `${cmd}.exe`));
|
|
977
|
+
}
|
|
978
|
+
return paths;
|
|
979
|
+
}
|
|
980
|
+
__name(getWindowsKnownPaths, "getWindowsKnownPaths");
|
|
981
|
+
function checkKnownPathsSync(cmd) {
|
|
982
|
+
for (const p of getWindowsKnownPaths(cmd)) {
|
|
983
|
+
try {
|
|
984
|
+
if (fs.existsSync(p)) return p;
|
|
985
|
+
} catch {
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
__name(checkKnownPathsSync, "checkKnownPathsSync");
|
|
991
|
+
var _userPathRefreshedAt = 0;
|
|
992
|
+
function refreshWindowsUserPath() {
|
|
993
|
+
if (process.platform !== "win32") return;
|
|
994
|
+
const now = Date.now();
|
|
995
|
+
if (now - _userPathRefreshedAt < 5e3) return;
|
|
996
|
+
_userPathRefreshedAt = now;
|
|
997
|
+
try {
|
|
998
|
+
const out = execSync(
|
|
999
|
+
`powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "[Environment]::GetEnvironmentVariable('Path', 'User')"`,
|
|
1000
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 }
|
|
1001
|
+
).trim();
|
|
1002
|
+
if (!out) return;
|
|
1003
|
+
const userParts = out.split(";").map((s) => s.trim()).filter(Boolean);
|
|
1004
|
+
const curParts = (process.env.PATH || "").split(";").map((s) => s.trim()).filter(Boolean);
|
|
1005
|
+
const curSet = new Set(curParts.map((s) => s.toLowerCase()));
|
|
1006
|
+
const added = [];
|
|
1007
|
+
for (const p of userParts) {
|
|
1008
|
+
if (!curSet.has(p.toLowerCase())) {
|
|
1009
|
+
curParts.push(p);
|
|
1010
|
+
added.push(p);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
if (added.length) {
|
|
1014
|
+
process.env.PATH = curParts.join(";");
|
|
1015
|
+
}
|
|
1016
|
+
} catch {
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
__name(refreshWindowsUserPath, "refreshWindowsUserPath");
|
|
1020
|
+
function commandExists(cmd) {
|
|
1021
|
+
if (process.platform === "win32") {
|
|
1022
|
+
const variants = [cmd, `${cmd}.cmd`, `${cmd}.exe`, `${cmd}.bat`];
|
|
1023
|
+
for (const variant of variants) {
|
|
1024
|
+
if (canRun(`where ${variant}`)) return true;
|
|
1025
|
+
}
|
|
1026
|
+
for (const variant of variants) {
|
|
1027
|
+
if (canRun(`cmd /d /s /c "${variant} --version"`, { timeout: 3e3 })) return true;
|
|
1028
|
+
}
|
|
1029
|
+
refreshWindowsUserPath();
|
|
1030
|
+
for (const variant of variants) {
|
|
1031
|
+
if (canRun(`where ${variant}`)) return true;
|
|
1032
|
+
}
|
|
1033
|
+
if (checkKnownPathsSync(cmd)) return true;
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
if (canRun(`which ${cmd}`)) return true;
|
|
1037
|
+
return canRun(`${cmd} --version`, { timeout: 3e3 });
|
|
1038
|
+
}
|
|
1039
|
+
__name(commandExists, "commandExists");
|
|
1040
|
+
async function commandExistsAsync(cmd) {
|
|
1041
|
+
if (process.platform === "win32") {
|
|
1042
|
+
const variants = [cmd, `${cmd}.cmd`, `${cmd}.exe`, `${cmd}.bat`];
|
|
1043
|
+
for (const variant of variants) {
|
|
1044
|
+
if (await canRunAsync(`where ${variant}`)) return true;
|
|
1045
|
+
}
|
|
1046
|
+
for (const variant of variants) {
|
|
1047
|
+
if (await canRunAsync(`cmd /d /s /c "${variant} --version"`)) return true;
|
|
1048
|
+
}
|
|
1049
|
+
refreshWindowsUserPath();
|
|
1050
|
+
for (const variant of variants) {
|
|
1051
|
+
if (await canRunAsync(`where ${variant}`)) return true;
|
|
1052
|
+
}
|
|
1053
|
+
if (checkKnownPathsSync(cmd)) return true;
|
|
1054
|
+
return false;
|
|
1055
|
+
}
|
|
1056
|
+
if (await canRunAsync(`which ${cmd}`)) return true;
|
|
1057
|
+
return canRunAsync(`${cmd} --version`);
|
|
1058
|
+
}
|
|
1059
|
+
__name(commandExistsAsync, "commandExistsAsync");
|
|
1060
|
+
module2.exports = {
|
|
1061
|
+
commandExists,
|
|
1062
|
+
commandExistsAsync,
|
|
1063
|
+
// test-only exports
|
|
1064
|
+
_getWindowsKnownPaths: getWindowsKnownPaths,
|
|
1065
|
+
_refreshWindowsUserPath: refreshWindowsUserPath
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
// src/tools/claude-code.js
|
|
1071
|
+
var require_claude_code = __commonJS({
|
|
1072
|
+
"src/tools/claude-code.js"(exports2, module2) {
|
|
1073
|
+
var fs = require("fs");
|
|
1074
|
+
var path = require("path");
|
|
1075
|
+
var os = require("os");
|
|
1076
|
+
var crypto = require("crypto");
|
|
1077
|
+
var { execSync } = require("child_process");
|
|
1078
|
+
var {
|
|
1079
|
+
BASE_URL_ANTHROPIC,
|
|
1080
|
+
BASE_URL_CLAUDE_RELAY
|
|
1081
|
+
} = require_config();
|
|
1082
|
+
var { readConfig, writeConfig } = require_claude_process_proxy();
|
|
1083
|
+
var SETTINGS_FILE = path.join(os.homedir(), ".claude", "settings.json");
|
|
1084
|
+
function buildBridgeConfig(apiKey, baseUrl, bridge = {}) {
|
|
1085
|
+
const relayUrl = bridge.relayUrl || BASE_URL_CLAUDE_RELAY || "";
|
|
1086
|
+
return {
|
|
1087
|
+
port: bridge.port || 14555,
|
|
1088
|
+
processProxyPort: bridge.processProxyPort || 14556,
|
|
1089
|
+
apiKey,
|
|
1090
|
+
baseUrlAnthropic: baseUrl || BASE_URL_ANTHROPIC,
|
|
1091
|
+
controlPlaneUrl: bridge.controlPlaneUrl || relayUrl || baseUrl || BASE_URL_ANTHROPIC,
|
|
1092
|
+
relayUrl: relayUrl || bridge.controlPlaneUrl || baseUrl || BASE_URL_ANTHROPIC,
|
|
1093
|
+
bridgeId: bridge.bridgeId || crypto.randomUUID(),
|
|
1094
|
+
deviceId: bridge.deviceId || crypto.randomUUID(),
|
|
1095
|
+
bridgeSecret: bridge.bridgeSecret || crypto.randomBytes(32).toString("hex"),
|
|
1096
|
+
nodeId: bridge.nodeId || "",
|
|
1097
|
+
sessionMode: bridge.sessionMode || "pinned-node",
|
|
1098
|
+
installSource: bridge.installSource || "holysheep-cli",
|
|
1099
|
+
proxyMode: "claude-process"
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
__name(buildBridgeConfig, "buildBridgeConfig");
|
|
1103
|
+
function readSettings() {
|
|
1104
|
+
try {
|
|
1105
|
+
const raw = fs.readFileSync(SETTINGS_FILE, "utf8");
|
|
1106
|
+
return JSON.parse(raw.replace(/[\x00-\x1F\x7F]/g, " "));
|
|
1107
|
+
} catch {
|
|
1108
|
+
return {};
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
__name(readSettings, "readSettings");
|
|
1112
|
+
function writeSettings(data) {
|
|
1113
|
+
const dir = path.dirname(SETTINGS_FILE);
|
|
1114
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
1115
|
+
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(data, null, 2), "utf8");
|
|
1116
|
+
}
|
|
1117
|
+
__name(writeSettings, "writeSettings");
|
|
1118
|
+
function cloneSettings(data) {
|
|
1119
|
+
return JSON.parse(JSON.stringify(data || {}));
|
|
1120
|
+
}
|
|
1121
|
+
__name(cloneSettings, "cloneSettings");
|
|
1122
|
+
function applyRuntimeSettingsOverride(overrides = {}) {
|
|
1123
|
+
const previous = readSettings();
|
|
1124
|
+
const next = cloneSettings(previous);
|
|
1125
|
+
next.env = { ...next.env || {} };
|
|
1126
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
1127
|
+
if (value === void 0 || value === null) {
|
|
1128
|
+
delete next.env[key];
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
next.env[key] = value;
|
|
1132
|
+
}
|
|
1133
|
+
writeSettings(next);
|
|
1134
|
+
return () => {
|
|
1135
|
+
writeSettings(previous);
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
__name(applyRuntimeSettingsOverride, "applyRuntimeSettingsOverride");
|
|
1139
|
+
function resolveCommandPath(cmd) {
|
|
1140
|
+
try {
|
|
1141
|
+
if (process.platform === "win32") {
|
|
1142
|
+
const out2 = execSync(`where ${cmd}`, { stdio: "pipe" }).toString().trim();
|
|
1143
|
+
const first2 = out2.split(/\r?\n/).find(Boolean);
|
|
1144
|
+
return first2 ? fs.realpathSync(first2) : null;
|
|
1145
|
+
}
|
|
1146
|
+
const out = execSync(`which ${cmd}`, { stdio: "pipe" }).toString().trim();
|
|
1147
|
+
const first = out.split(/\r?\n/).find(Boolean);
|
|
1148
|
+
return first ? fs.realpathSync(first) : null;
|
|
1149
|
+
} catch {
|
|
1150
|
+
return null;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
__name(resolveCommandPath, "resolveCommandPath");
|
|
1154
|
+
function detectBinaryFormat(buffer) {
|
|
1155
|
+
if (!buffer || buffer.length < 4) return null;
|
|
1156
|
+
if (buffer[0] === 35 && buffer[1] === 33) return "script";
|
|
1157
|
+
const magic = buffer.slice(0, 4).toString("hex");
|
|
1158
|
+
if (magic === "7f454c46") return "elf";
|
|
1159
|
+
if (magic === "feedface" || magic === "feedfacf" || magic === "cefaedfe" || magic === "cffaedfe") return "mach-o";
|
|
1160
|
+
if (buffer[0] === 77 && buffer[1] === 90) return "pe";
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1163
|
+
__name(detectBinaryFormat, "detectBinaryFormat");
|
|
1164
|
+
function detectClaudeRuntime() {
|
|
1165
|
+
const executablePath = resolveCommandPath("claude");
|
|
1166
|
+
if (!executablePath) {
|
|
1167
|
+
return {
|
|
1168
|
+
available: false,
|
|
1169
|
+
path: null,
|
|
1170
|
+
kind: "missing",
|
|
1171
|
+
launchMode: "unknown",
|
|
1172
|
+
display: "\u672A\u627E\u5230 claude"
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
try {
|
|
1176
|
+
const fd = fs.openSync(executablePath, "r");
|
|
1177
|
+
const buffer = Buffer.alloc(16);
|
|
1178
|
+
fs.readSync(fd, buffer, 0, buffer.length, 0);
|
|
1179
|
+
fs.closeSync(fd);
|
|
1180
|
+
const format = detectBinaryFormat(buffer);
|
|
1181
|
+
if (format === "script") {
|
|
1182
|
+
return {
|
|
1183
|
+
available: true,
|
|
1184
|
+
path: executablePath,
|
|
1185
|
+
kind: "script",
|
|
1186
|
+
launchMode: "node-inject",
|
|
1187
|
+
display: "\u811A\u672C\u5165\u53E3\uFF08\u4F7F\u7528 NODE_OPTIONS \u6CE8\u5165\uFF09"
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
if (format) {
|
|
1191
|
+
return {
|
|
1192
|
+
available: true,
|
|
1193
|
+
path: executablePath,
|
|
1194
|
+
kind: "binary",
|
|
1195
|
+
launchMode: "env-proxy",
|
|
1196
|
+
display: `\u72EC\u7ACB\u4E8C\u8FDB\u5236\uFF08${format}\uFF0C\u4F7F\u7528 HTTP(S)_PROXY\uFF09`
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
} catch {
|
|
1200
|
+
}
|
|
1201
|
+
return {
|
|
1202
|
+
available: true,
|
|
1203
|
+
path: executablePath,
|
|
1204
|
+
kind: "unknown",
|
|
1205
|
+
launchMode: "env-proxy",
|
|
1206
|
+
display: "\u672A\u77E5\u5165\u53E3\u7C7B\u578B\uFF08\u9ED8\u8BA4\u4F7F\u7528 HTTP(S)_PROXY\uFF09"
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
__name(detectClaudeRuntime, "detectClaudeRuntime");
|
|
1210
|
+
module2.exports = {
|
|
1211
|
+
name: "Claude Code",
|
|
1212
|
+
id: "claude-code",
|
|
1213
|
+
checkInstalled() {
|
|
1214
|
+
return require_which().commandExists("claude");
|
|
1215
|
+
},
|
|
1216
|
+
isConfigured() {
|
|
1217
|
+
var _a;
|
|
1218
|
+
const s = readSettings();
|
|
1219
|
+
const bridge = readConfig();
|
|
1220
|
+
return Boolean(
|
|
1221
|
+
((_a = s.env) == null ? void 0 : _a.ANTHROPIC_AUTH_TOKEN) && bridge.apiKey && bridge.bridgeSecret && bridge.controlPlaneUrl && bridge.bridgeId && bridge.deviceId && bridge.processProxyPort
|
|
1222
|
+
);
|
|
1223
|
+
},
|
|
1224
|
+
configure(apiKey, baseUrl, baseUrlOpenAI, primaryModel) {
|
|
1225
|
+
const bridgeConfig = buildBridgeConfig(apiKey, baseUrl, readConfig());
|
|
1226
|
+
writeConfig(bridgeConfig);
|
|
1227
|
+
const settings = readSettings();
|
|
1228
|
+
if (!settings.env) settings.env = {};
|
|
1229
|
+
settings.env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
1230
|
+
delete settings.env.ANTHROPIC_BASE_URL;
|
|
1231
|
+
settings.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1";
|
|
1232
|
+
settings.env.HOLYSHEEP_CLAUDE_LAUNCHER = "hs";
|
|
1233
|
+
if (primaryModel) settings.model = primaryModel;
|
|
1234
|
+
delete settings.env.ANTHROPIC_API_KEY;
|
|
1235
|
+
delete settings.env.HOLYSHEEP_CLAUDE_BRIDGE;
|
|
1236
|
+
writeSettings(settings);
|
|
1237
|
+
return {
|
|
1238
|
+
file: SETTINGS_FILE,
|
|
1239
|
+
hot: true,
|
|
1240
|
+
extraFiles: ["~/.holysheep/claude-proxy.json"]
|
|
1241
|
+
};
|
|
1242
|
+
},
|
|
1243
|
+
reset() {
|
|
1244
|
+
const settings = readSettings();
|
|
1245
|
+
if (settings.env) {
|
|
1246
|
+
delete settings.env.ANTHROPIC_AUTH_TOKEN;
|
|
1247
|
+
delete settings.env.ANTHROPIC_API_KEY;
|
|
1248
|
+
delete settings.env.ANTHROPIC_BASE_URL;
|
|
1249
|
+
delete settings.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC;
|
|
1250
|
+
delete settings.env.HOLYSHEEP_CLAUDE_BRIDGE;
|
|
1251
|
+
delete settings.env.HOLYSHEEP_CLAUDE_LAUNCHER;
|
|
1252
|
+
}
|
|
1253
|
+
writeSettings(settings);
|
|
1254
|
+
},
|
|
1255
|
+
getConfigPath() {
|
|
1256
|
+
return SETTINGS_FILE;
|
|
1257
|
+
},
|
|
1258
|
+
hint: "\u901A\u8FC7 hs claude \u4EE5\u6574\u8FDB\u7A0B\u4EE3\u7406\u65B9\u5F0F\u542F\u52A8 Claude Code",
|
|
1259
|
+
launchCmd: "hs claude",
|
|
1260
|
+
installCmd: process.platform === "win32" ? "irm https://claude.ai/install.ps1 | iex" : "curl -fsSL https://claude.ai/install.sh | bash",
|
|
1261
|
+
docsUrl: "https://docs.anthropic.com/claude-code",
|
|
1262
|
+
getProcessProxyConfig() {
|
|
1263
|
+
return readConfig();
|
|
1264
|
+
},
|
|
1265
|
+
buildBridgeConfig,
|
|
1266
|
+
detectClaudeRuntime,
|
|
1267
|
+
readSettings,
|
|
1268
|
+
writeSettings,
|
|
1269
|
+
applyRuntimeSettingsOverride
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
// src/tools/codex.js
|
|
1275
|
+
var require_codex = __commonJS({
|
|
1276
|
+
"src/tools/codex.js"(exports2, module2) {
|
|
1277
|
+
var fs = require("fs");
|
|
1278
|
+
var path = require("path");
|
|
1279
|
+
var os = require("os");
|
|
1280
|
+
var CONFIG_DIR = path.join(os.homedir(), ".codex");
|
|
1281
|
+
var CONFIG_FILE = path.join(CONFIG_DIR, "config.toml");
|
|
1282
|
+
var CONFIG_FILE_JSON = path.join(CONFIG_DIR, "config.json");
|
|
1283
|
+
var AUTH_FILE = path.join(CONFIG_DIR, "auth.json");
|
|
1284
|
+
function normalizeToml(content) {
|
|
1285
|
+
return String(content || "").replace(/\r\n/g, "\n");
|
|
1286
|
+
}
|
|
1287
|
+
__name(normalizeToml, "normalizeToml");
|
|
1288
|
+
function cleanupToml(content) {
|
|
1289
|
+
return normalizeToml(content).replace(/\n{3,}/g, "\n\n").trim();
|
|
1290
|
+
}
|
|
1291
|
+
__name(cleanupToml, "cleanupToml");
|
|
1292
|
+
function stripManagedTomlConfig(content) {
|
|
1293
|
+
const lines = normalizeToml(content).split("\n");
|
|
1294
|
+
const output = [];
|
|
1295
|
+
let currentSection = null;
|
|
1296
|
+
let skipHolySheepBlock = false;
|
|
1297
|
+
for (const line of lines) {
|
|
1298
|
+
const trimmed = line.trim();
|
|
1299
|
+
if (/^\[[^\]]+\]$/.test(trimmed)) {
|
|
1300
|
+
if (trimmed === "[model_providers.holysheep]" || trimmed === "[model_providers.holysheep.http_headers]") {
|
|
1301
|
+
currentSection = trimmed;
|
|
1302
|
+
skipHolySheepBlock = true;
|
|
1303
|
+
continue;
|
|
1304
|
+
}
|
|
1305
|
+
currentSection = trimmed;
|
|
1306
|
+
skipHolySheepBlock = false;
|
|
1307
|
+
}
|
|
1308
|
+
if (skipHolySheepBlock) continue;
|
|
1309
|
+
if (!currentSection) {
|
|
1310
|
+
if (/^model\s*=\s*"[^"]*"\s*$/.test(trimmed)) continue;
|
|
1311
|
+
if (/^model_provider\s*=/.test(trimmed)) continue;
|
|
1312
|
+
if (/^preferred_auth_method\s*=/.test(trimmed)) continue;
|
|
1313
|
+
}
|
|
1314
|
+
output.push(line);
|
|
1315
|
+
}
|
|
1316
|
+
return cleanupToml(output.join("\n"));
|
|
1317
|
+
}
|
|
1318
|
+
__name(stripManagedTomlConfig, "stripManagedTomlConfig");
|
|
1319
|
+
function readTomlConfig() {
|
|
1320
|
+
try {
|
|
1321
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
1322
|
+
return fs.readFileSync(CONFIG_FILE, "utf8");
|
|
1323
|
+
}
|
|
1324
|
+
} catch {
|
|
1325
|
+
}
|
|
1326
|
+
return "";
|
|
1327
|
+
}
|
|
1328
|
+
__name(readTomlConfig, "readTomlConfig");
|
|
1329
|
+
function isConfiguredInToml() {
|
|
1330
|
+
const content = readTomlConfig();
|
|
1331
|
+
return content.includes('model_provider = "holysheep"') && content.includes("base_url") && content.includes("holysheep.ai");
|
|
1332
|
+
}
|
|
1333
|
+
__name(isConfiguredInToml, "isConfiguredInToml");
|
|
1334
|
+
function writeTomlConfig(apiKey, baseUrlOpenAI, model) {
|
|
1335
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
1336
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
1337
|
+
}
|
|
1338
|
+
const content = stripManagedTomlConfig(readTomlConfig());
|
|
1339
|
+
const newConfig = [
|
|
1340
|
+
`model = "${model || "gpt-5.4"}"`,
|
|
1341
|
+
`model_provider = "holysheep"`,
|
|
1342
|
+
"",
|
|
1343
|
+
content,
|
|
1344
|
+
"",
|
|
1345
|
+
`[model_providers.holysheep]`,
|
|
1346
|
+
`name = "HolySheep"`,
|
|
1347
|
+
`base_url = "${baseUrlOpenAI}"`,
|
|
1348
|
+
`wire_api = "responses"`,
|
|
1349
|
+
"",
|
|
1350
|
+
`[model_providers.holysheep.http_headers]`,
|
|
1351
|
+
`Authorization = "Bearer ${apiKey}"`,
|
|
1352
|
+
""
|
|
1353
|
+
].join("\n");
|
|
1354
|
+
fs.writeFileSync(CONFIG_FILE, cleanupToml(newConfig) + "\n", "utf8");
|
|
1355
|
+
}
|
|
1356
|
+
__name(writeTomlConfig, "writeTomlConfig");
|
|
1357
|
+
function writeJsonConfigIfNeeded(apiKey, baseUrlOpenAI, model) {
|
|
1358
|
+
try {
|
|
1359
|
+
let jsonConfig = {};
|
|
1360
|
+
if (fs.existsSync(CONFIG_FILE_JSON)) {
|
|
1361
|
+
jsonConfig = JSON.parse(fs.readFileSync(CONFIG_FILE_JSON, "utf8"));
|
|
1362
|
+
}
|
|
1363
|
+
jsonConfig.model = model || "gpt-5.4";
|
|
1364
|
+
jsonConfig.model_provider = "holysheep";
|
|
1365
|
+
jsonConfig.provider = "holysheep";
|
|
1366
|
+
if (!jsonConfig.model_providers) jsonConfig.model_providers = {};
|
|
1367
|
+
if (!jsonConfig.providers) jsonConfig.providers = {};
|
|
1368
|
+
jsonConfig.model_providers.holysheep = {
|
|
1369
|
+
name: "HolySheep",
|
|
1370
|
+
base_url: baseUrlOpenAI,
|
|
1371
|
+
api_key: apiKey,
|
|
1372
|
+
wire_api: "responses"
|
|
1373
|
+
};
|
|
1374
|
+
jsonConfig.providers.holysheep = {
|
|
1375
|
+
name: "HolySheep",
|
|
1376
|
+
baseURL: baseUrlOpenAI,
|
|
1377
|
+
apiKey
|
|
1378
|
+
};
|
|
1379
|
+
fs.writeFileSync(CONFIG_FILE_JSON, JSON.stringify(jsonConfig, null, 2), "utf8");
|
|
1380
|
+
} catch {
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
__name(writeJsonConfigIfNeeded, "writeJsonConfigIfNeeded");
|
|
1384
|
+
function neutralizeAuthJson() {
|
|
1385
|
+
const MAX_ATTEMPTS = process.platform === "win32" ? 5 : 1;
|
|
1386
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
1387
|
+
try {
|
|
1388
|
+
fs.unlinkSync(AUTH_FILE);
|
|
1389
|
+
return;
|
|
1390
|
+
} catch (e) {
|
|
1391
|
+
if (e && e.code === "ENOENT") return;
|
|
1392
|
+
if (attempt === MAX_ATTEMPTS) {
|
|
1393
|
+
try {
|
|
1394
|
+
fs.writeFileSync(AUTH_FILE, "{}\n", "utf8");
|
|
1395
|
+
} catch {
|
|
1396
|
+
}
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
try {
|
|
1400
|
+
require("child_process").execSync(
|
|
1401
|
+
process.platform === "win32" ? 'powershell -NoProfile -Command "Start-Sleep -Milliseconds 200"' : "sleep 0.2",
|
|
1402
|
+
{ stdio: "ignore" }
|
|
1403
|
+
);
|
|
1404
|
+
} catch {
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
__name(neutralizeAuthJson, "neutralizeAuthJson");
|
|
1410
|
+
module2.exports = {
|
|
1411
|
+
name: "Codex CLI",
|
|
1412
|
+
id: "codex",
|
|
1413
|
+
checkInstalled() {
|
|
1414
|
+
return require_which().commandExists("codex");
|
|
1415
|
+
},
|
|
1416
|
+
isConfigured() {
|
|
1417
|
+
return isConfiguredInToml();
|
|
1418
|
+
},
|
|
1419
|
+
configure(apiKey, _baseUrlAnthropicNoV1, baseUrlOpenAI) {
|
|
1420
|
+
const model = "gpt-5.4";
|
|
1421
|
+
writeTomlConfig(apiKey, baseUrlOpenAI, model);
|
|
1422
|
+
writeJsonConfigIfNeeded(apiKey, baseUrlOpenAI, model);
|
|
1423
|
+
neutralizeAuthJson();
|
|
1424
|
+
return {
|
|
1425
|
+
file: CONFIG_FILE,
|
|
1426
|
+
hot: false
|
|
1427
|
+
};
|
|
1428
|
+
},
|
|
1429
|
+
reset() {
|
|
1430
|
+
var _a, _b;
|
|
1431
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
1432
|
+
const content = stripManagedTomlConfig(readTomlConfig());
|
|
1433
|
+
fs.writeFileSync(CONFIG_FILE, content ? content + "\n" : "", "utf8");
|
|
1434
|
+
}
|
|
1435
|
+
if (fs.existsSync(CONFIG_FILE_JSON)) {
|
|
1436
|
+
try {
|
|
1437
|
+
const c = JSON.parse(fs.readFileSync(CONFIG_FILE_JSON, "utf8"));
|
|
1438
|
+
if (c.model_provider === "holysheep") {
|
|
1439
|
+
delete c.model_provider;
|
|
1440
|
+
}
|
|
1441
|
+
if (c.provider === "holysheep") {
|
|
1442
|
+
delete c.provider;
|
|
1443
|
+
}
|
|
1444
|
+
(_a = c.model_providers) == null ? true : delete _a.holysheep;
|
|
1445
|
+
(_b = c.providers) == null ? true : delete _b.holysheep;
|
|
1446
|
+
if (c.model_providers && Object.keys(c.model_providers).length === 0) delete c.model_providers;
|
|
1447
|
+
if (c.providers && Object.keys(c.providers).length === 0) delete c.providers;
|
|
1448
|
+
fs.writeFileSync(CONFIG_FILE_JSON, JSON.stringify(c, null, 2), "utf8");
|
|
1449
|
+
} catch {
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
},
|
|
1453
|
+
getConfigPath() {
|
|
1454
|
+
return CONFIG_FILE;
|
|
1455
|
+
},
|
|
1456
|
+
hint: "\u5207\u6362\u540E\u91CD\u5F00\u7EC8\u7AEF\u751F\u6548\uFF1BRust Codex (v0.111+) \u4F7F\u7528 config.toml",
|
|
1457
|
+
launchCmd: "codex",
|
|
1458
|
+
installCmd: "npm install -g @openai/codex",
|
|
1459
|
+
docsUrl: "https://github.com/openai/codex",
|
|
1460
|
+
envVarFormat: "openai"
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
// src/tools/droid.js
|
|
1466
|
+
var require_droid = __commonJS({
|
|
1467
|
+
"src/tools/droid.js"(exports2, module2) {
|
|
1468
|
+
var fs = require("fs");
|
|
1469
|
+
var path = require("path");
|
|
1470
|
+
var os = require("os");
|
|
1471
|
+
var CONFIG_DIR = path.join(os.homedir(), ".factory");
|
|
1472
|
+
var SETTINGS_FILE = path.join(CONFIG_DIR, "settings.json");
|
|
1473
|
+
var LEGACY_CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
1474
|
+
function installCmdForPlatform() {
|
|
1475
|
+
if (process.platform === "win32") {
|
|
1476
|
+
return "winget install --id Factory.Droid -e --accept-source-agreements --accept-package-agreements";
|
|
1477
|
+
}
|
|
1478
|
+
if (process.platform === "darwin") {
|
|
1479
|
+
return "brew install --cask droid";
|
|
1480
|
+
}
|
|
1481
|
+
return "curl -fsSL https://app.factory.ai/install.sh | bash";
|
|
1482
|
+
}
|
|
1483
|
+
__name(installCmdForPlatform, "installCmdForPlatform");
|
|
1484
|
+
var DEFAULT_MODELS = [
|
|
1485
|
+
{
|
|
1486
|
+
model: "gpt-5.4",
|
|
1487
|
+
id: "custom:gpt-5.4-0",
|
|
1488
|
+
baseUrlSuffix: "",
|
|
1489
|
+
displayName: "GPT-5.4",
|
|
1490
|
+
provider: "openai",
|
|
1491
|
+
route: "openai"
|
|
1492
|
+
},
|
|
1493
|
+
{
|
|
1494
|
+
model: "claude-sonnet-4-6",
|
|
1495
|
+
id: "custom:claude-sonnet-4-6-0",
|
|
1496
|
+
baseUrlSuffix: "",
|
|
1497
|
+
displayName: "Sonnet 4.6",
|
|
1498
|
+
provider: "anthropic",
|
|
1499
|
+
route: "anthropic"
|
|
1500
|
+
},
|
|
1501
|
+
{
|
|
1502
|
+
model: "claude-opus-4-6",
|
|
1503
|
+
id: "custom:claude-opus-4-6-0",
|
|
1504
|
+
baseUrlSuffix: "",
|
|
1505
|
+
displayName: "Opus 4.6",
|
|
1506
|
+
provider: "anthropic",
|
|
1507
|
+
route: "anthropic"
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
model: "MiniMax-M2.7-highspeed",
|
|
1511
|
+
id: "custom:MiniMax-M2.7-highspeed-0",
|
|
1512
|
+
baseUrlSuffix: "/minimax",
|
|
1513
|
+
displayName: "MiniMax 2.7 Highspeed",
|
|
1514
|
+
provider: "anthropic",
|
|
1515
|
+
route: "anthropic"
|
|
1516
|
+
},
|
|
1517
|
+
{
|
|
1518
|
+
model: "claude-haiku-4-5",
|
|
1519
|
+
id: "custom:claude-haiku-4-5-0",
|
|
1520
|
+
baseUrlSuffix: "",
|
|
1521
|
+
displayName: "Haiku 4.5",
|
|
1522
|
+
provider: "anthropic",
|
|
1523
|
+
route: "anthropic"
|
|
1524
|
+
}
|
|
1525
|
+
];
|
|
1526
|
+
function readSettings() {
|
|
1527
|
+
try {
|
|
1528
|
+
if (fs.existsSync(SETTINGS_FILE)) {
|
|
1529
|
+
return JSON.parse(fs.readFileSync(SETTINGS_FILE, "utf8"));
|
|
1530
|
+
}
|
|
1531
|
+
} catch {
|
|
1532
|
+
}
|
|
1533
|
+
return {};
|
|
1534
|
+
}
|
|
1535
|
+
__name(readSettings, "readSettings");
|
|
1536
|
+
function writeSettings(data) {
|
|
1537
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
1538
|
+
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(data, null, 2), "utf8");
|
|
1539
|
+
}
|
|
1540
|
+
__name(writeSettings, "writeSettings");
|
|
1541
|
+
function readLegacyConfig() {
|
|
1542
|
+
try {
|
|
1543
|
+
if (fs.existsSync(LEGACY_CONFIG_FILE)) {
|
|
1544
|
+
return JSON.parse(fs.readFileSync(LEGACY_CONFIG_FILE, "utf8"));
|
|
1545
|
+
}
|
|
1546
|
+
} catch {
|
|
1547
|
+
}
|
|
1548
|
+
return {};
|
|
1549
|
+
}
|
|
1550
|
+
__name(readLegacyConfig, "readLegacyConfig");
|
|
1551
|
+
function writeLegacyConfig(data) {
|
|
1552
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
1553
|
+
fs.writeFileSync(LEGACY_CONFIG_FILE, JSON.stringify(data, null, 2), "utf8");
|
|
1554
|
+
}
|
|
1555
|
+
__name(writeLegacyConfig, "writeLegacyConfig");
|
|
1556
|
+
function isHolySheepModel(item) {
|
|
1557
|
+
return typeof (item == null ? void 0 : item.baseUrl) === "string" && item.baseUrl.includes("api.holysheep.ai");
|
|
1558
|
+
}
|
|
1559
|
+
__name(isHolySheepModel, "isHolySheepModel");
|
|
1560
|
+
function normalizeSelectedModels(selectedModels) {
|
|
1561
|
+
const selected = new Set(
|
|
1562
|
+
Array.isArray(selectedModels) && selectedModels.length > 0 ? selectedModels : DEFAULT_MODELS.map((item) => item.model)
|
|
1563
|
+
);
|
|
1564
|
+
const models = DEFAULT_MODELS.filter((item) => selected.has(item.model)).map((item, index) => ({
|
|
1565
|
+
model: item.model,
|
|
1566
|
+
id: item.id,
|
|
1567
|
+
index,
|
|
1568
|
+
baseUrlSuffix: item.baseUrlSuffix,
|
|
1569
|
+
displayName: item.displayName,
|
|
1570
|
+
provider: item.provider,
|
|
1571
|
+
route: item.route
|
|
1572
|
+
}));
|
|
1573
|
+
return models.length > 0 ? models : DEFAULT_MODELS.map((item, index) => ({ ...item, index }));
|
|
1574
|
+
}
|
|
1575
|
+
__name(normalizeSelectedModels, "normalizeSelectedModels");
|
|
1576
|
+
function buildCustomModels(apiKey, baseUrlAnthropic, baseUrlOpenAI, selectedModels) {
|
|
1577
|
+
const anthropicRootUrl = String(baseUrlAnthropic || "").replace(/\/+$/, "");
|
|
1578
|
+
const openaiRootUrl = String(baseUrlOpenAI || "").replace(/\/+$/, "");
|
|
1579
|
+
return normalizeSelectedModels(selectedModels).map((item) => ({
|
|
1580
|
+
model: item.model,
|
|
1581
|
+
id: item.id,
|
|
1582
|
+
index: item.index,
|
|
1583
|
+
baseUrl: item.route === "openai" ? `${openaiRootUrl}${item.baseUrlSuffix}` : `${anthropicRootUrl}${item.baseUrlSuffix}`,
|
|
1584
|
+
apiKey,
|
|
1585
|
+
displayName: item.displayName,
|
|
1586
|
+
maxOutputTokens: 64e3,
|
|
1587
|
+
noImageSupport: true,
|
|
1588
|
+
provider: item.provider
|
|
1589
|
+
}));
|
|
1590
|
+
}
|
|
1591
|
+
__name(buildCustomModels, "buildCustomModels");
|
|
1592
|
+
module2.exports = {
|
|
1593
|
+
name: "Droid CLI",
|
|
1594
|
+
id: "droid",
|
|
1595
|
+
checkInstalled() {
|
|
1596
|
+
return require_which().commandExists("droid");
|
|
1597
|
+
},
|
|
1598
|
+
isConfigured() {
|
|
1599
|
+
const settings = readSettings();
|
|
1600
|
+
const customModels = Array.isArray(settings.customModels) ? settings.customModels : [];
|
|
1601
|
+
if (customModels.some(isHolySheepModel)) return true;
|
|
1602
|
+
const legacy = readLegacyConfig();
|
|
1603
|
+
const legacyModels = Array.isArray(legacy.customModels) ? legacy.customModels : [];
|
|
1604
|
+
return legacyModels.some(isHolySheepModel);
|
|
1605
|
+
},
|
|
1606
|
+
configure(apiKey, baseUrlAnthropic, baseUrlOpenAI, _primaryModel, selectedModels) {
|
|
1607
|
+
const nextModels = buildCustomModels(apiKey, baseUrlAnthropic, baseUrlOpenAI, selectedModels);
|
|
1608
|
+
const preferredDefault = nextModels.find((m) => m.id === "custom:claude-sonnet-4-6-0") || nextModels.find((m) => /sonnet/i.test(m.model)) || nextModels[0];
|
|
1609
|
+
const defaultModelId = preferredDefault ? preferredDefault.id : null;
|
|
1610
|
+
const settings = readSettings();
|
|
1611
|
+
const preservedModels = Array.isArray(settings.customModels) ? settings.customModels.filter((item) => !isHolySheepModel(item)) : [];
|
|
1612
|
+
settings.customModels = [
|
|
1613
|
+
...nextModels,
|
|
1614
|
+
...preservedModels
|
|
1615
|
+
];
|
|
1616
|
+
settings.logoAnimation = "off";
|
|
1617
|
+
if (defaultModelId) {
|
|
1618
|
+
settings.sessionDefaultSettings = {
|
|
1619
|
+
...settings.sessionDefaultSettings || {},
|
|
1620
|
+
model: defaultModelId
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
writeSettings(settings);
|
|
1624
|
+
const legacy = readLegacyConfig();
|
|
1625
|
+
const preservedLegacyModels = Array.isArray(legacy.customModels) ? legacy.customModels.filter((item) => !isHolySheepModel(item)) : [];
|
|
1626
|
+
legacy.customModels = [
|
|
1627
|
+
...nextModels,
|
|
1628
|
+
...preservedLegacyModels
|
|
1629
|
+
];
|
|
1630
|
+
legacy.logoAnimation = "off";
|
|
1631
|
+
if (defaultModelId) {
|
|
1632
|
+
legacy.sessionDefaultSettings = {
|
|
1633
|
+
...legacy.sessionDefaultSettings || {},
|
|
1634
|
+
model: defaultModelId
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
writeLegacyConfig(legacy);
|
|
1638
|
+
if (process.platform === "win32") {
|
|
1639
|
+
try {
|
|
1640
|
+
const { execSync } = require("child_process");
|
|
1641
|
+
execSync(`setx FACTORY_API_KEY "${apiKey}"`, { stdio: "ignore", timeout: 1e4 });
|
|
1642
|
+
} catch {
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
return {
|
|
1646
|
+
file: SETTINGS_FILE,
|
|
1647
|
+
hot: true
|
|
1648
|
+
};
|
|
1649
|
+
},
|
|
1650
|
+
reset() {
|
|
1651
|
+
const settings = readSettings();
|
|
1652
|
+
const hsIds = new Set(
|
|
1653
|
+
(Array.isArray(settings.customModels) ? settings.customModels : []).filter(isHolySheepModel).map((m) => m == null ? void 0 : m.id).filter(Boolean)
|
|
1654
|
+
);
|
|
1655
|
+
if (Array.isArray(settings.customModels)) {
|
|
1656
|
+
settings.customModels = settings.customModels.filter((item) => !isHolySheepModel(item));
|
|
1657
|
+
}
|
|
1658
|
+
if (settings.sessionDefaultSettings && typeof settings.sessionDefaultSettings === "object" && hsIds.has(settings.sessionDefaultSettings.model)) {
|
|
1659
|
+
delete settings.sessionDefaultSettings.model;
|
|
1660
|
+
}
|
|
1661
|
+
writeSettings(settings);
|
|
1662
|
+
const legacy = readLegacyConfig();
|
|
1663
|
+
const hsLegacyIds = new Set(
|
|
1664
|
+
(Array.isArray(legacy.customModels) ? legacy.customModels : []).filter(isHolySheepModel).map((m) => m == null ? void 0 : m.id).filter(Boolean)
|
|
1665
|
+
);
|
|
1666
|
+
if (Array.isArray(legacy.customModels)) {
|
|
1667
|
+
legacy.customModels = legacy.customModels.filter((item) => !isHolySheepModel(item));
|
|
1668
|
+
}
|
|
1669
|
+
if (legacy.sessionDefaultSettings && typeof legacy.sessionDefaultSettings === "object" && hsLegacyIds.has(legacy.sessionDefaultSettings.model)) {
|
|
1670
|
+
delete legacy.sessionDefaultSettings.model;
|
|
1671
|
+
}
|
|
1672
|
+
writeLegacyConfig(legacy);
|
|
1673
|
+
},
|
|
1674
|
+
getConfigPath() {
|
|
1675
|
+
return SETTINGS_FILE;
|
|
1676
|
+
},
|
|
1677
|
+
hint: "\u5DF2\u5199\u5165 ~/.factory/settings.json\uFF1B\u91CD\u542F Droid \u540E\u53EF\u89C1 HolySheep \u6A21\u578B\u5217\u8868",
|
|
1678
|
+
launchCmd: "droid",
|
|
1679
|
+
installCmd: installCmdForPlatform(),
|
|
1680
|
+
docsUrl: "https://docs.factory.ai/cli/getting-started/overview"
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
});
|
|
1684
|
+
|
|
1685
|
+
// src/tools/opencode.js
|
|
1686
|
+
var require_opencode = __commonJS({
|
|
1687
|
+
"src/tools/opencode.js"(exports2, module2) {
|
|
1688
|
+
var fs = require("fs");
|
|
1689
|
+
var path = require("path");
|
|
1690
|
+
var os = require("os");
|
|
1691
|
+
function getConfigFile() {
|
|
1692
|
+
const candidates = [
|
|
1693
|
+
// 新版标准路径(官方文档)
|
|
1694
|
+
path.join(os.homedir(), ".config", "opencode", "opencode.json"),
|
|
1695
|
+
// 旧版路径兼容
|
|
1696
|
+
path.join(os.homedir(), ".config", "opencode", "config.json"),
|
|
1697
|
+
path.join(os.homedir(), ".opencode", "opencode.json"),
|
|
1698
|
+
path.join(os.homedir(), ".opencode", "config.json"),
|
|
1699
|
+
// Windows
|
|
1700
|
+
path.join(os.homedir(), "AppData", "Roaming", "opencode", "opencode.json")
|
|
1701
|
+
];
|
|
1702
|
+
for (const f of candidates) {
|
|
1703
|
+
if (fs.existsSync(f)) return f;
|
|
1704
|
+
}
|
|
1705
|
+
return path.join(os.homedir(), ".config", "opencode", "opencode.json");
|
|
1706
|
+
}
|
|
1707
|
+
__name(getConfigFile, "getConfigFile");
|
|
1708
|
+
function readConfig() {
|
|
1709
|
+
const file = getConfigFile();
|
|
1710
|
+
try {
|
|
1711
|
+
if (fs.existsSync(file)) {
|
|
1712
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1713
|
+
return JSON.parse(content.replace(/^\s*\/\/[^\n]*/gm, "").replace(/\/\*[\s\S]*?\*\//g, ""));
|
|
1714
|
+
}
|
|
1715
|
+
} catch {
|
|
1716
|
+
}
|
|
1717
|
+
return {};
|
|
1718
|
+
}
|
|
1719
|
+
__name(readConfig, "readConfig");
|
|
1720
|
+
function writeConfig(data) {
|
|
1721
|
+
const file = getConfigFile();
|
|
1722
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
1723
|
+
fs.writeFileSync(file, JSON.stringify(data, null, 2), "utf8");
|
|
1724
|
+
}
|
|
1725
|
+
__name(writeConfig, "writeConfig");
|
|
1726
|
+
function getAuthFileCandidates() {
|
|
1727
|
+
const home = os.homedir();
|
|
1728
|
+
if (process.platform === "win32") {
|
|
1729
|
+
return [
|
|
1730
|
+
path.join(process.env.LOCALAPPDATA || path.join(home, "AppData", "Local"), "opencode", "auth.json"),
|
|
1731
|
+
path.join(home, ".local", "share", "opencode", "auth.json")
|
|
1732
|
+
];
|
|
1733
|
+
}
|
|
1734
|
+
const xdg = process.env.XDG_DATA_HOME || path.join(home, ".local", "share");
|
|
1735
|
+
return [
|
|
1736
|
+
path.join(xdg, "opencode", "auth.json"),
|
|
1737
|
+
path.join(home, ".local", "share", "opencode", "auth.json"),
|
|
1738
|
+
path.join(home, "Library", "Application Support", "opencode", "auth.json")
|
|
1739
|
+
];
|
|
1740
|
+
}
|
|
1741
|
+
__name(getAuthFileCandidates, "getAuthFileCandidates");
|
|
1742
|
+
function purgeOauthAuth() {
|
|
1743
|
+
const touched = [];
|
|
1744
|
+
let keysAfter = [];
|
|
1745
|
+
for (const file of getAuthFileCandidates()) {
|
|
1746
|
+
try {
|
|
1747
|
+
if (!fs.existsSync(file)) continue;
|
|
1748
|
+
const raw = fs.readFileSync(file, "utf8");
|
|
1749
|
+
let data;
|
|
1750
|
+
try {
|
|
1751
|
+
data = JSON.parse(raw);
|
|
1752
|
+
} catch {
|
|
1753
|
+
continue;
|
|
1754
|
+
}
|
|
1755
|
+
if (!data || typeof data !== "object") continue;
|
|
1756
|
+
let changed = false;
|
|
1757
|
+
for (const key of ["anthropic", "openai"]) {
|
|
1758
|
+
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
1759
|
+
delete data[key];
|
|
1760
|
+
changed = true;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
if (!changed) continue;
|
|
1764
|
+
touched.push(file);
|
|
1765
|
+
const remaining = Object.keys(data);
|
|
1766
|
+
keysAfter = remaining;
|
|
1767
|
+
if (remaining.length === 0) {
|
|
1768
|
+
try {
|
|
1769
|
+
fs.unlinkSync(file);
|
|
1770
|
+
} catch {
|
|
1771
|
+
}
|
|
1772
|
+
} else {
|
|
1773
|
+
fs.writeFileSync(file, JSON.stringify(data, null, 2), "utf8");
|
|
1774
|
+
}
|
|
1775
|
+
} catch {
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
return { touched, keysAfter };
|
|
1779
|
+
}
|
|
1780
|
+
__name(purgeOauthAuth, "purgeOauthAuth");
|
|
1781
|
+
module2.exports = {
|
|
1782
|
+
name: "OpenCode",
|
|
1783
|
+
id: "opencode",
|
|
1784
|
+
checkInstalled() {
|
|
1785
|
+
return require_which().commandExists("opencode");
|
|
1786
|
+
},
|
|
1787
|
+
isConfigured() {
|
|
1788
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1789
|
+
const c = readConfig();
|
|
1790
|
+
return !!(((_d = (_c = (_b = (_a = c.provider) == null ? void 0 : _a.anthropic) == null ? void 0 : _b.options) == null ? void 0 : _c.baseURL) == null ? void 0 : _d.includes("holysheep")) || ((_h = (_g = (_f = (_e = c.provider) == null ? void 0 : _e.openai) == null ? void 0 : _f.options) == null ? void 0 : _g.baseURL) == null ? void 0 : _h.includes("holysheep")));
|
|
1791
|
+
},
|
|
1792
|
+
configure(apiKey, baseUrlAnthropicNoV1, baseUrlOpenAI, primaryModel) {
|
|
1793
|
+
try {
|
|
1794
|
+
purgeOauthAuth();
|
|
1795
|
+
} catch {
|
|
1796
|
+
}
|
|
1797
|
+
const config = readConfig();
|
|
1798
|
+
if (!config.provider) config.provider = {};
|
|
1799
|
+
if (!config["$schema"]) config["$schema"] = "https://opencode.ai/config.json";
|
|
1800
|
+
config.provider.anthropic = {
|
|
1801
|
+
options: {
|
|
1802
|
+
baseURL: `${String(baseUrlAnthropicNoV1 || "").replace(/\/+$/, "")}/v1`,
|
|
1803
|
+
apiKey
|
|
1804
|
+
}
|
|
1805
|
+
};
|
|
1806
|
+
config.provider.openai = {
|
|
1807
|
+
options: {
|
|
1808
|
+
baseURL: baseUrlOpenAI,
|
|
1809
|
+
// https://api.holysheep.ai/v1
|
|
1810
|
+
apiKey
|
|
1811
|
+
}
|
|
1812
|
+
};
|
|
1813
|
+
const DEFAULT_MODEL = "openai/gpt-5.4";
|
|
1814
|
+
const MODEL_WHITELIST = /^(openai\/)?gpt-[\w][\w.\-]*$/i;
|
|
1815
|
+
let resolvedModel = DEFAULT_MODEL;
|
|
1816
|
+
const envOverride = (process.env.HOLYSHEEP_OPENCODE_MODEL || "").trim();
|
|
1817
|
+
if (envOverride) {
|
|
1818
|
+
if (MODEL_WHITELIST.test(envOverride)) {
|
|
1819
|
+
resolvedModel = /^openai\//i.test(envOverride) ? envOverride : `openai/${envOverride}`;
|
|
1820
|
+
} else {
|
|
1821
|
+
console.error(
|
|
1822
|
+
`[opencode] HOLYSHEEP_OPENCODE_MODEL="${envOverride}" rejected \u2014 must match openai/gpt-* or gpt-* (non-empty suffix). falling back to ${DEFAULT_MODEL}.`
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
const previousModel = typeof config.model === "string" ? config.model : "";
|
|
1827
|
+
if (previousModel && /^(anthropic\/|claude-)/i.test(previousModel) && previousModel !== resolvedModel) {
|
|
1828
|
+
console.error(
|
|
1829
|
+
`[opencode] migrated ${previousModel} \u2192 ${resolvedModel} (hs28 body-size fix: claude-* trips 40KB plan-limit via relay)`
|
|
1830
|
+
);
|
|
1831
|
+
}
|
|
1832
|
+
config.model = resolvedModel;
|
|
1833
|
+
void primaryModel;
|
|
1834
|
+
if (!process.env.HOLYSHEEP_OPENCODE_KEEP_ALL_TOOLS) {
|
|
1835
|
+
const tools = { ...config.tools || {} };
|
|
1836
|
+
if (tools.todowrite === void 0) tools.todowrite = false;
|
|
1837
|
+
if (tools.todoread === void 0) tools.todoread = false;
|
|
1838
|
+
if (tools.task === void 0) tools.task = false;
|
|
1839
|
+
if (tools.skill === void 0) tools.skill = false;
|
|
1840
|
+
if (tools.webfetch === void 0) tools.webfetch = false;
|
|
1841
|
+
config.tools = tools;
|
|
1842
|
+
}
|
|
1843
|
+
writeConfig(config);
|
|
1844
|
+
return { file: getConfigFile(), hot: false };
|
|
1845
|
+
},
|
|
1846
|
+
reset() {
|
|
1847
|
+
const config = readConfig();
|
|
1848
|
+
if (config.provider) {
|
|
1849
|
+
delete config.provider.anthropic;
|
|
1850
|
+
delete config.provider.openai;
|
|
1851
|
+
}
|
|
1852
|
+
writeConfig(config);
|
|
1853
|
+
try {
|
|
1854
|
+
purgeOauthAuth();
|
|
1855
|
+
} catch {
|
|
1856
|
+
}
|
|
1857
|
+
},
|
|
1858
|
+
getConfigPath() {
|
|
1859
|
+
return getConfigFile();
|
|
1860
|
+
},
|
|
1861
|
+
hint: "\u5207\u6362\u540E\u91CD\u542F OpenCode \u751F\u6548\uFF1B\u914D\u7F6E\u6587\u4EF6: ~/.config/opencode/opencode.json",
|
|
1862
|
+
launchCmd: "opencode",
|
|
1863
|
+
installCmd: "brew install anomalyco/tap/opencode # \u6216: npm i -g opencode-ai@latest",
|
|
1864
|
+
docsUrl: "https://opencode.ai",
|
|
1865
|
+
// [HolySheep fork v2.1.36 / hs25] Exposed for tests — see tests/opencode-auth-purge.test.js
|
|
1866
|
+
_purgeOauthAuth: purgeOauthAuth,
|
|
1867
|
+
_getAuthFileCandidates: getAuthFileCandidates
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
});
|
|
1871
|
+
|
|
1872
|
+
// src/tools/openclaw-bridge.js
|
|
1873
|
+
var require_openclaw_bridge = __commonJS({
|
|
1874
|
+
"src/tools/openclaw-bridge.js"(exports2, module2) {
|
|
1875
|
+
"use strict";
|
|
1876
|
+
var fs = require("fs");
|
|
1877
|
+
var http = require("http");
|
|
1878
|
+
var path = require("path");
|
|
1879
|
+
var os = require("os");
|
|
1880
|
+
var fetch = global.fetch || require("node-fetch");
|
|
1881
|
+
var _nodeFetch = require("node-fetch");
|
|
1882
|
+
function upstreamFetch(url, options) {
|
|
1883
|
+
if (process.platform === "win32" && String(url).startsWith("https://")) {
|
|
1884
|
+
const https = require("https");
|
|
1885
|
+
return _nodeFetch(url, { ...options, agent: new https.Agent({ family: 4 }) });
|
|
1886
|
+
}
|
|
1887
|
+
return fetch(url, options);
|
|
1888
|
+
}
|
|
1889
|
+
__name(upstreamFetch, "upstreamFetch");
|
|
1890
|
+
var OPENCLAW_DIR = path.join(os.homedir(), ".openclaw");
|
|
1891
|
+
var BRIDGE_CONFIG_FILE = path.join(OPENCLAW_DIR, "holysheep-bridge.json");
|
|
1892
|
+
var DEFAULT_WATCHDOG_INTERVAL_MS = 3e3;
|
|
1893
|
+
var DEFAULT_WATCHDOG_FAILURE_THRESHOLD = 3;
|
|
1894
|
+
var DEFAULT_WATCHDOG_STARTUP_GRACE_MS = 3e4;
|
|
1895
|
+
var DEFAULT_WATCHDOG_REQUEST_TIMEOUT_MS = 1500;
|
|
1896
|
+
function readBridgeConfig(configPath = BRIDGE_CONFIG_FILE) {
|
|
1897
|
+
return JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
1898
|
+
}
|
|
1899
|
+
__name(readBridgeConfig, "readBridgeConfig");
|
|
1900
|
+
function parseArgs(argv) {
|
|
1901
|
+
const args = { port: null, host: "127.0.0.1", config: BRIDGE_CONFIG_FILE };
|
|
1902
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1903
|
+
const value = argv[i];
|
|
1904
|
+
if (value === "--port") args.port = Number(argv[++i]);
|
|
1905
|
+
else if (value === "--host") args.host = argv[++i];
|
|
1906
|
+
else if (value === "--config") args.config = argv[++i];
|
|
1907
|
+
}
|
|
1908
|
+
return args;
|
|
1909
|
+
}
|
|
1910
|
+
__name(parseArgs, "parseArgs");
|
|
1911
|
+
function readJsonBody(req) {
|
|
1912
|
+
return new Promise((resolve, reject) => {
|
|
1913
|
+
let raw = "";
|
|
1914
|
+
req.on("data", (chunk) => {
|
|
1915
|
+
raw += chunk;
|
|
1916
|
+
if (raw.length > 5 * 1024 * 1024) {
|
|
1917
|
+
reject(new Error("Request body too large"));
|
|
1918
|
+
req.destroy();
|
|
1919
|
+
}
|
|
1920
|
+
});
|
|
1921
|
+
req.on("end", () => {
|
|
1922
|
+
if (!raw) return resolve({});
|
|
1923
|
+
try {
|
|
1924
|
+
resolve(JSON.parse(raw));
|
|
1925
|
+
} catch (error) {
|
|
1926
|
+
reject(error);
|
|
1927
|
+
}
|
|
1928
|
+
});
|
|
1929
|
+
req.on("error", reject);
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
__name(readJsonBody, "readJsonBody");
|
|
1933
|
+
function sendJson(res, statusCode, payload) {
|
|
1934
|
+
res.writeHead(statusCode, {
|
|
1935
|
+
"content-type": "application/json; charset=utf-8",
|
|
1936
|
+
"cache-control": "no-store"
|
|
1937
|
+
});
|
|
1938
|
+
res.end(JSON.stringify(payload));
|
|
1939
|
+
}
|
|
1940
|
+
__name(sendJson, "sendJson");
|
|
1941
|
+
function sendOpenAIStream(res, payload) {
|
|
1942
|
+
var _a;
|
|
1943
|
+
const choice = ((_a = payload.choices) == null ? void 0 : _a[0]) || {};
|
|
1944
|
+
const message = choice.message || {};
|
|
1945
|
+
const created = payload.created || Math.floor(Date.now() / 1e3);
|
|
1946
|
+
const messageContent = extractOpenAITextContent(message.content);
|
|
1947
|
+
res.writeHead(200, {
|
|
1948
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
1949
|
+
"cache-control": "no-cache, no-transform",
|
|
1950
|
+
connection: "keep-alive"
|
|
1951
|
+
});
|
|
1952
|
+
const firstChunk = {
|
|
1953
|
+
id: payload.id,
|
|
1954
|
+
object: "chat.completion.chunk",
|
|
1955
|
+
created,
|
|
1956
|
+
model: payload.model,
|
|
1957
|
+
choices: [{
|
|
1958
|
+
index: 0,
|
|
1959
|
+
delta: {
|
|
1960
|
+
role: "assistant",
|
|
1961
|
+
...messageContent ? { content: messageContent } : {},
|
|
1962
|
+
...message.tool_calls ? { tool_calls: message.tool_calls } : {}
|
|
1963
|
+
},
|
|
1964
|
+
finish_reason: null
|
|
1965
|
+
}]
|
|
1966
|
+
};
|
|
1967
|
+
const finalChunk = {
|
|
1968
|
+
id: payload.id,
|
|
1969
|
+
object: "chat.completion.chunk",
|
|
1970
|
+
created,
|
|
1971
|
+
model: payload.model,
|
|
1972
|
+
choices: [{ index: 0, delta: {}, finish_reason: choice.finish_reason || "stop" }],
|
|
1973
|
+
usage: payload.usage
|
|
1974
|
+
};
|
|
1975
|
+
res.write(`data: ${JSON.stringify(firstChunk)}
|
|
1976
|
+
|
|
1977
|
+
`);
|
|
1978
|
+
res.write(`data: ${JSON.stringify(finalChunk)}
|
|
1979
|
+
|
|
1980
|
+
`);
|
|
1981
|
+
res.end("data: [DONE]\n\n");
|
|
1982
|
+
}
|
|
1983
|
+
__name(sendOpenAIStream, "sendOpenAIStream");
|
|
1984
|
+
function normalizeText(value) {
|
|
1985
|
+
if (typeof value === "string") return value;
|
|
1986
|
+
if (Array.isArray(value)) return value.map(normalizeText).filter(Boolean).join("\n");
|
|
1987
|
+
if (value && typeof value === "object") {
|
|
1988
|
+
if (typeof value.text === "string") return value.text;
|
|
1989
|
+
if (typeof value.output_text === "string") return value.output_text;
|
|
1990
|
+
if (typeof value.content === "string") return value.content;
|
|
1991
|
+
if (typeof value.value === "string") return value.value;
|
|
1992
|
+
}
|
|
1993
|
+
return value == null ? "" : String(value);
|
|
1994
|
+
}
|
|
1995
|
+
__name(normalizeText, "normalizeText");
|
|
1996
|
+
function extractOpenAITextContent(content) {
|
|
1997
|
+
if (typeof content === "string") return content;
|
|
1998
|
+
if (!Array.isArray(content)) return normalizeText(content);
|
|
1999
|
+
return content.map((part) => {
|
|
2000
|
+
if (typeof part === "string") return part;
|
|
2001
|
+
if (!part || typeof part !== "object") return "";
|
|
2002
|
+
if (part.type === "text") return normalizeText(part.text);
|
|
2003
|
+
if (part.type === "output_text") return normalizeText(part.text);
|
|
2004
|
+
if (part.type === "input_text") return normalizeText(part.text);
|
|
2005
|
+
return normalizeText(part.text || part.content || part.value);
|
|
2006
|
+
}).filter(Boolean).join("");
|
|
2007
|
+
}
|
|
2008
|
+
__name(extractOpenAITextContent, "extractOpenAITextContent");
|
|
2009
|
+
function parseDataUrl(url) {
|
|
2010
|
+
const match = String(url || "").match(/^data:([^;]+);base64,(.+)$/);
|
|
2011
|
+
if (!match) return null;
|
|
2012
|
+
return { mediaType: match[1], data: match[2] };
|
|
2013
|
+
}
|
|
2014
|
+
__name(parseDataUrl, "parseDataUrl");
|
|
2015
|
+
function openAIContentToAnthropicBlocks(content) {
|
|
2016
|
+
var _a;
|
|
2017
|
+
if (typeof content === "string") return [{ type: "text", text: content }];
|
|
2018
|
+
if (!Array.isArray(content)) return [];
|
|
2019
|
+
const blocks = [];
|
|
2020
|
+
for (const part of content) {
|
|
2021
|
+
if (!part) continue;
|
|
2022
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
2023
|
+
blocks.push({ type: "text", text: part.text });
|
|
2024
|
+
continue;
|
|
2025
|
+
}
|
|
2026
|
+
if (part.type === "image_url" && ((_a = part.image_url) == null ? void 0 : _a.url)) {
|
|
2027
|
+
const dataUrl = parseDataUrl(part.image_url.url);
|
|
2028
|
+
if (dataUrl) {
|
|
2029
|
+
blocks.push({
|
|
2030
|
+
type: "image",
|
|
2031
|
+
source: { type: "base64", media_type: dataUrl.mediaType, data: dataUrl.data }
|
|
2032
|
+
});
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
return blocks;
|
|
2037
|
+
}
|
|
2038
|
+
__name(openAIContentToAnthropicBlocks, "openAIContentToAnthropicBlocks");
|
|
2039
|
+
function pushAnthropicMessage(messages, role, blocks) {
|
|
2040
|
+
if (!blocks.length) return;
|
|
2041
|
+
const previous = messages[messages.length - 1];
|
|
2042
|
+
if (previous && previous.role === role) {
|
|
2043
|
+
previous.content = previous.content.concat(blocks);
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
messages.push({ role, content: blocks });
|
|
2047
|
+
}
|
|
2048
|
+
__name(pushAnthropicMessage, "pushAnthropicMessage");
|
|
2049
|
+
function convertOpenAIToAnthropicMessages(messages) {
|
|
2050
|
+
var _a, _b;
|
|
2051
|
+
const anthropicMessages = [];
|
|
2052
|
+
const systemParts = [];
|
|
2053
|
+
for (const message of messages || []) {
|
|
2054
|
+
if (!message) continue;
|
|
2055
|
+
if (message.role === "system") {
|
|
2056
|
+
const blocks2 = openAIContentToAnthropicBlocks(message.content);
|
|
2057
|
+
if (blocks2.length === 0) {
|
|
2058
|
+
const text = normalizeText(message.content);
|
|
2059
|
+
if (text) systemParts.push(text);
|
|
2060
|
+
} else {
|
|
2061
|
+
for (const block of blocks2) {
|
|
2062
|
+
if (block.type === "text") systemParts.push(block.text);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
continue;
|
|
2066
|
+
}
|
|
2067
|
+
if (message.role === "tool") {
|
|
2068
|
+
pushAnthropicMessage(anthropicMessages, "user", [{
|
|
2069
|
+
type: "tool_result",
|
|
2070
|
+
tool_use_id: message.tool_call_id,
|
|
2071
|
+
content: normalizeText(message.content)
|
|
2072
|
+
}]);
|
|
2073
|
+
continue;
|
|
2074
|
+
}
|
|
2075
|
+
if (message.role === "assistant") {
|
|
2076
|
+
const blocks2 = [];
|
|
2077
|
+
const textBlocks = openAIContentToAnthropicBlocks(message.content);
|
|
2078
|
+
if (textBlocks.length) blocks2.push(...textBlocks);
|
|
2079
|
+
else if (typeof message.content === "string" && message.content) blocks2.push({ type: "text", text: message.content });
|
|
2080
|
+
for (const toolCall of message.tool_calls || []) {
|
|
2081
|
+
let input = {};
|
|
2082
|
+
try {
|
|
2083
|
+
input = JSON.parse(((_a = toolCall.function) == null ? void 0 : _a.arguments) || "{}");
|
|
2084
|
+
} catch {
|
|
2085
|
+
}
|
|
2086
|
+
blocks2.push({
|
|
2087
|
+
type: "tool_use",
|
|
2088
|
+
id: toolCall.id,
|
|
2089
|
+
name: ((_b = toolCall.function) == null ? void 0 : _b.name) || "tool",
|
|
2090
|
+
input
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
2093
|
+
pushAnthropicMessage(anthropicMessages, "assistant", blocks2);
|
|
2094
|
+
continue;
|
|
2095
|
+
}
|
|
2096
|
+
const blocks = openAIContentToAnthropicBlocks(message.content);
|
|
2097
|
+
if (blocks.length) pushAnthropicMessage(anthropicMessages, "user", blocks);
|
|
2098
|
+
else {
|
|
2099
|
+
const text = normalizeText(message.content);
|
|
2100
|
+
if (text) pushAnthropicMessage(anthropicMessages, "user", [{ type: "text", text }]);
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
return {
|
|
2104
|
+
system: systemParts.join("\n\n").trim() || void 0,
|
|
2105
|
+
messages: anthropicMessages
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
__name(convertOpenAIToAnthropicMessages, "convertOpenAIToAnthropicMessages");
|
|
2109
|
+
function convertOpenAIToolsToAnthropic(tools) {
|
|
2110
|
+
return (tools || []).filter((tool) => {
|
|
2111
|
+
var _a;
|
|
2112
|
+
return (tool == null ? void 0 : tool.type) === "function" && ((_a = tool.function) == null ? void 0 : _a.name);
|
|
2113
|
+
}).map((tool) => ({
|
|
2114
|
+
name: tool.function.name,
|
|
2115
|
+
description: tool.function.description || "",
|
|
2116
|
+
input_schema: tool.function.parameters || { type: "object", properties: {} }
|
|
2117
|
+
}));
|
|
2118
|
+
}
|
|
2119
|
+
__name(convertOpenAIToolsToAnthropic, "convertOpenAIToolsToAnthropic");
|
|
2120
|
+
function convertToolChoice(toolChoice) {
|
|
2121
|
+
var _a;
|
|
2122
|
+
if (!toolChoice || toolChoice === "auto") return { type: "auto" };
|
|
2123
|
+
if (toolChoice === "none") return { type: "auto", disable_parallel_tool_use: true };
|
|
2124
|
+
if (toolChoice === "required") return { type: "any" };
|
|
2125
|
+
if (toolChoice.type === "function" && ((_a = toolChoice.function) == null ? void 0 : _a.name)) {
|
|
2126
|
+
return { type: "tool", name: toolChoice.function.name };
|
|
2127
|
+
}
|
|
2128
|
+
return { type: "auto" };
|
|
2129
|
+
}
|
|
2130
|
+
__name(convertToolChoice, "convertToolChoice");
|
|
2131
|
+
function buildAnthropicPayload(requestBody, stream = false) {
|
|
2132
|
+
const converted = convertOpenAIToAnthropicMessages(requestBody.messages);
|
|
2133
|
+
const payload = {
|
|
2134
|
+
model: requestBody.model,
|
|
2135
|
+
max_tokens: requestBody.max_tokens || requestBody.max_completion_tokens || requestBody.max_output_tokens || 4096,
|
|
2136
|
+
messages: converted.messages,
|
|
2137
|
+
stream: Boolean(stream)
|
|
2138
|
+
};
|
|
2139
|
+
if (converted.system) payload.system = converted.system;
|
|
2140
|
+
if (requestBody.temperature != null) payload.temperature = requestBody.temperature;
|
|
2141
|
+
if (requestBody.top_p != null) payload.top_p = requestBody.top_p;
|
|
2142
|
+
if (Array.isArray(requestBody.stop) && requestBody.stop.length) payload.stop_sequences = requestBody.stop;
|
|
2143
|
+
if (typeof requestBody.stop === "string") payload.stop_sequences = [requestBody.stop];
|
|
2144
|
+
const tools = convertOpenAIToolsToAnthropic(requestBody.tools);
|
|
2145
|
+
if (tools.length) payload.tools = tools;
|
|
2146
|
+
if (requestBody.tool_choice) payload.tool_choice = convertToolChoice(requestBody.tool_choice);
|
|
2147
|
+
return payload;
|
|
2148
|
+
}
|
|
2149
|
+
__name(buildAnthropicPayload, "buildAnthropicPayload");
|
|
2150
|
+
function mapFinishReason(stopReason) {
|
|
2151
|
+
if (stopReason === "tool_use") return "tool_calls";
|
|
2152
|
+
if (stopReason === "max_tokens") return "length";
|
|
2153
|
+
return "stop";
|
|
2154
|
+
}
|
|
2155
|
+
__name(mapFinishReason, "mapFinishReason");
|
|
2156
|
+
function buildToolCalls(content) {
|
|
2157
|
+
const calls = [];
|
|
2158
|
+
for (const block of content || []) {
|
|
2159
|
+
if ((block == null ? void 0 : block.type) !== "tool_use") continue;
|
|
2160
|
+
calls.push({
|
|
2161
|
+
id: block.id,
|
|
2162
|
+
type: "function",
|
|
2163
|
+
function: {
|
|
2164
|
+
name: block.name,
|
|
2165
|
+
arguments: JSON.stringify(block.input || {})
|
|
2166
|
+
}
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
return calls;
|
|
2170
|
+
}
|
|
2171
|
+
__name(buildToolCalls, "buildToolCalls");
|
|
2172
|
+
function anthropicToOpenAIResponse(responseBody, requestedModel) {
|
|
2173
|
+
const text = (responseBody.content || []).filter((block) => (block == null ? void 0 : block.type) === "text").map((block) => block.text).join("");
|
|
2174
|
+
const toolCalls = buildToolCalls(responseBody.content);
|
|
2175
|
+
return {
|
|
2176
|
+
id: responseBody.id || `chatcmpl_${Date.now()}`,
|
|
2177
|
+
object: "chat.completion",
|
|
2178
|
+
created: Math.floor(Date.now() / 1e3),
|
|
2179
|
+
model: requestedModel,
|
|
2180
|
+
choices: [{
|
|
2181
|
+
index: 0,
|
|
2182
|
+
message: {
|
|
2183
|
+
role: "assistant",
|
|
2184
|
+
content: text || null,
|
|
2185
|
+
...toolCalls.length ? { tool_calls: toolCalls } : {}
|
|
2186
|
+
},
|
|
2187
|
+
finish_reason: mapFinishReason(responseBody.stop_reason)
|
|
2188
|
+
}],
|
|
2189
|
+
usage: responseBody.usage ? {
|
|
2190
|
+
prompt_tokens: responseBody.usage.input_tokens || 0,
|
|
2191
|
+
completion_tokens: responseBody.usage.output_tokens || 0,
|
|
2192
|
+
total_tokens: (responseBody.usage.input_tokens || 0) + (responseBody.usage.output_tokens || 0)
|
|
2193
|
+
} : void 0
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
__name(anthropicToOpenAIResponse, "anthropicToOpenAIResponse");
|
|
2197
|
+
function pickRoute(model) {
|
|
2198
|
+
if (String(model).startsWith("gpt-")) return "openai";
|
|
2199
|
+
if (String(model).startsWith("claude-")) return "anthropic";
|
|
2200
|
+
if (String(model).startsWith("MiniMax-")) return "minimax";
|
|
2201
|
+
return "openai";
|
|
2202
|
+
}
|
|
2203
|
+
__name(pickRoute, "pickRoute");
|
|
2204
|
+
function responseOutputToText(output) {
|
|
2205
|
+
return (output || []).flatMap((item) => {
|
|
2206
|
+
if ((item == null ? void 0 : item.type) === "message") return item.content || [];
|
|
2207
|
+
if (item == null ? void 0 : item.content) return item.content;
|
|
2208
|
+
return [];
|
|
2209
|
+
}).filter((item) => (item == null ? void 0 : item.type) === "output_text" || (item == null ? void 0 : item.type) === "text").map((item) => extractOpenAITextContent(item.text || item.content || item)).filter(Boolean).join("");
|
|
2210
|
+
}
|
|
2211
|
+
__name(responseOutputToText, "responseOutputToText");
|
|
2212
|
+
function responseOutputToToolCalls(output) {
|
|
2213
|
+
return (output || []).filter((item) => (item == null ? void 0 : item.type) === "function_call" && item.name).map((item, index) => ({
|
|
2214
|
+
id: item.call_id || item.id || `call_${index + 1}`,
|
|
2215
|
+
type: "function",
|
|
2216
|
+
function: {
|
|
2217
|
+
name: item.name,
|
|
2218
|
+
arguments: typeof item.arguments === "string" ? item.arguments : JSON.stringify(item.arguments || {})
|
|
2219
|
+
}
|
|
2220
|
+
}));
|
|
2221
|
+
}
|
|
2222
|
+
__name(responseOutputToToolCalls, "responseOutputToToolCalls");
|
|
2223
|
+
function responseToChatCompletion(responseBody, requestedModel) {
|
|
2224
|
+
var _a, _b, _c, _d;
|
|
2225
|
+
const response = (responseBody == null ? void 0 : responseBody.response) && typeof responseBody.response === "object" ? responseBody.response : responseBody;
|
|
2226
|
+
const text = responseOutputToText(response.output);
|
|
2227
|
+
const toolCalls = responseOutputToToolCalls(response.output);
|
|
2228
|
+
const status = String(response.status || "").toLowerCase();
|
|
2229
|
+
const finishReason = status === "completed" || status === "" ? "stop" : "length";
|
|
2230
|
+
const outputTokens = ((_a = response.usage) == null ? void 0 : _a.output_tokens) || ((_b = response.usage) == null ? void 0 : _b.completion_tokens) || 0;
|
|
2231
|
+
const promptTokens = ((_c = response.usage) == null ? void 0 : _c.input_tokens) || ((_d = response.usage) == null ? void 0 : _d.prompt_tokens) || 0;
|
|
2232
|
+
return {
|
|
2233
|
+
id: response.id || `chatcmpl_${Date.now()}`,
|
|
2234
|
+
object: "chat.completion",
|
|
2235
|
+
created: response.created_at || Math.floor(Date.now() / 1e3),
|
|
2236
|
+
model: requestedModel || response.model,
|
|
2237
|
+
choices: [{
|
|
2238
|
+
index: 0,
|
|
2239
|
+
message: {
|
|
2240
|
+
role: "assistant",
|
|
2241
|
+
content: text || null,
|
|
2242
|
+
...toolCalls.length ? { tool_calls: toolCalls } : {}
|
|
2243
|
+
},
|
|
2244
|
+
finish_reason: finishReason
|
|
2245
|
+
}],
|
|
2246
|
+
usage: response.usage ? {
|
|
2247
|
+
prompt_tokens: promptTokens,
|
|
2248
|
+
completion_tokens: outputTokens,
|
|
2249
|
+
total_tokens: response.usage.total_tokens || promptTokens + outputTokens
|
|
2250
|
+
} : void 0
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
__name(responseToChatCompletion, "responseToChatCompletion");
|
|
2254
|
+
function normalizeOpenAICompatibleResponse(parsed, requestedModel) {
|
|
2255
|
+
var _a, _b;
|
|
2256
|
+
if (!parsed || typeof parsed !== "object") return parsed;
|
|
2257
|
+
if (parsed.object === "response" || Array.isArray(parsed.output)) {
|
|
2258
|
+
return responseToChatCompletion(parsed, requestedModel);
|
|
2259
|
+
}
|
|
2260
|
+
if (parsed.object === "chat.completion" || parsed.object === "chat.completion.chunk") {
|
|
2261
|
+
const choice = (_a = parsed.choices) == null ? void 0 : _a[0];
|
|
2262
|
+
if (choice == null ? void 0 : choice.message) {
|
|
2263
|
+
choice.message = {
|
|
2264
|
+
...choice.message,
|
|
2265
|
+
content: extractOpenAITextContent(choice.message.content) || null
|
|
2266
|
+
};
|
|
2267
|
+
}
|
|
2268
|
+
if (((_b = choice == null ? void 0 : choice.delta) == null ? void 0 : _b.content) != null) {
|
|
2269
|
+
choice.delta = {
|
|
2270
|
+
...choice.delta,
|
|
2271
|
+
content: extractOpenAITextContent(choice.delta.content)
|
|
2272
|
+
};
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
return parsed;
|
|
2276
|
+
}
|
|
2277
|
+
__name(normalizeOpenAICompatibleResponse, "normalizeOpenAICompatibleResponse");
|
|
2278
|
+
function parseOpenAIStreamText(text, requestedModel) {
|
|
2279
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
2280
|
+
try {
|
|
2281
|
+
const parsed = JSON.parse(String(text || ""));
|
|
2282
|
+
if (parsed && typeof parsed === "object") {
|
|
2283
|
+
return normalizeOpenAICompatibleResponse(parsed, requestedModel);
|
|
2284
|
+
}
|
|
2285
|
+
} catch {
|
|
2286
|
+
}
|
|
2287
|
+
const blocks = String(text || "").split(/\r?\n\r?\n+/).filter(Boolean);
|
|
2288
|
+
let responseCompleted = null;
|
|
2289
|
+
let finalChunk = null;
|
|
2290
|
+
let content = "";
|
|
2291
|
+
let sawOutputTextDelta = false;
|
|
2292
|
+
for (const block of blocks) {
|
|
2293
|
+
const eventMatch = block.match(/^event:\s*(.+)$/m);
|
|
2294
|
+
const dataMatch = block.match(/^data:\s*(.+)$/m);
|
|
2295
|
+
if (!dataMatch) continue;
|
|
2296
|
+
const eventName = eventMatch ? eventMatch[1].trim() : "";
|
|
2297
|
+
const payload = dataMatch[1].trim();
|
|
2298
|
+
if (!payload || payload === "[DONE]") continue;
|
|
2299
|
+
let chunk;
|
|
2300
|
+
try {
|
|
2301
|
+
chunk = JSON.parse(payload);
|
|
2302
|
+
} catch {
|
|
2303
|
+
continue;
|
|
2304
|
+
}
|
|
2305
|
+
if (eventName === "response.output_text.delta" && typeof chunk.delta === "string") {
|
|
2306
|
+
sawOutputTextDelta = true;
|
|
2307
|
+
content += chunk.delta;
|
|
2308
|
+
continue;
|
|
2309
|
+
}
|
|
2310
|
+
if (eventName === "response.content_part.done" && ((_a = chunk.part) == null ? void 0 : _a.type) === "output_text" && typeof chunk.part.text === "string") {
|
|
2311
|
+
if (!sawOutputTextDelta) content += chunk.part.text;
|
|
2312
|
+
continue;
|
|
2313
|
+
}
|
|
2314
|
+
if (eventName === "response.completed" && chunk.response) {
|
|
2315
|
+
responseCompleted = chunk.response;
|
|
2316
|
+
if (!content) {
|
|
2317
|
+
const outputText = responseOutputToText(chunk.response.output);
|
|
2318
|
+
if (outputText) content = outputText;
|
|
2319
|
+
}
|
|
2320
|
+
continue;
|
|
2321
|
+
}
|
|
2322
|
+
finalChunk = chunk;
|
|
2323
|
+
const choice = ((_b = chunk.choices) == null ? void 0 : _b[0]) || {};
|
|
2324
|
+
const delta = choice.delta || {};
|
|
2325
|
+
const deltaContent = extractOpenAITextContent(delta.content);
|
|
2326
|
+
const messageContent = extractOpenAITextContent((_c = choice.message) == null ? void 0 : _c.content);
|
|
2327
|
+
if (deltaContent) content += deltaContent;
|
|
2328
|
+
else if (messageContent) content += messageContent;
|
|
2329
|
+
}
|
|
2330
|
+
if (responseCompleted) {
|
|
2331
|
+
const completion = responseToChatCompletion(responseCompleted, requestedModel || responseCompleted.model);
|
|
2332
|
+
if (!((_f = (_e = (_d = completion.choices) == null ? void 0 : _d[0]) == null ? void 0 : _e.message) == null ? void 0 : _f.content) && content) {
|
|
2333
|
+
completion.choices[0].message.content = content;
|
|
2334
|
+
}
|
|
2335
|
+
return completion;
|
|
2336
|
+
}
|
|
2337
|
+
if (!finalChunk) return null;
|
|
2338
|
+
return normalizeOpenAICompatibleResponse({
|
|
2339
|
+
id: finalChunk.id || `chatcmpl_${Date.now()}`,
|
|
2340
|
+
object: "chat.completion",
|
|
2341
|
+
created: finalChunk.created || Math.floor(Date.now() / 1e3),
|
|
2342
|
+
model: finalChunk.model,
|
|
2343
|
+
choices: [{
|
|
2344
|
+
index: 0,
|
|
2345
|
+
message: { role: "assistant", content: content || null },
|
|
2346
|
+
finish_reason: ((_h = (_g = finalChunk.choices) == null ? void 0 : _g[0]) == null ? void 0 : _h.finish_reason) || "stop"
|
|
2347
|
+
}],
|
|
2348
|
+
usage: finalChunk.usage
|
|
2349
|
+
}, requestedModel);
|
|
2350
|
+
}
|
|
2351
|
+
__name(parseOpenAIStreamText, "parseOpenAIStreamText");
|
|
2352
|
+
async function relayOpenAIRequest(requestBody, config, res) {
|
|
2353
|
+
const upstreamBody = {
|
|
2354
|
+
...requestBody,
|
|
2355
|
+
stream: requestBody.stream === true
|
|
2356
|
+
};
|
|
2357
|
+
const upstream = await upstreamFetch(`${config.baseUrlOpenAI.replace(/\/+$/, "")}/chat/completions`, {
|
|
2358
|
+
method: "POST",
|
|
2359
|
+
headers: {
|
|
2360
|
+
"content-type": "application/json",
|
|
2361
|
+
authorization: `Bearer ${config.apiKey}`,
|
|
2362
|
+
"user-agent": "holysheep-openclaw-bridge/1.0"
|
|
2363
|
+
},
|
|
2364
|
+
body: JSON.stringify(upstreamBody)
|
|
2365
|
+
});
|
|
2366
|
+
if (requestBody.stream === true && upstream.ok) {
|
|
2367
|
+
res.writeHead(200, {
|
|
2368
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
2369
|
+
"cache-control": "no-cache, no-transform",
|
|
2370
|
+
connection: "keep-alive"
|
|
2371
|
+
});
|
|
2372
|
+
try {
|
|
2373
|
+
await pipeStream(upstream.body, (chunk) => res.write(chunk));
|
|
2374
|
+
} catch {
|
|
2375
|
+
}
|
|
2376
|
+
if (!res.writableEnded) res.end();
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
const text = await upstream.text();
|
|
2380
|
+
const parsed = parseOpenAIStreamText(text, requestBody.model);
|
|
2381
|
+
if (upstream.ok && parsed) {
|
|
2382
|
+
if (requestBody.stream) return sendOpenAIStream(res, parsed);
|
|
2383
|
+
return sendJson(res, upstream.status, parsed);
|
|
2384
|
+
}
|
|
2385
|
+
res.writeHead(upstream.status, {
|
|
2386
|
+
"content-type": upstream.headers.get("content-type") || "application/json; charset=utf-8",
|
|
2387
|
+
"cache-control": upstream.headers.get("cache-control") || "no-store"
|
|
2388
|
+
});
|
|
2389
|
+
res.end(text);
|
|
2390
|
+
}
|
|
2391
|
+
__name(relayOpenAIRequest, "relayOpenAIRequest");
|
|
2392
|
+
async function pipeStream(body, onChunk) {
|
|
2393
|
+
if (body == null) return;
|
|
2394
|
+
if (typeof body.getReader === "function") {
|
|
2395
|
+
const reader = body.getReader();
|
|
2396
|
+
const decoder = new TextDecoder();
|
|
2397
|
+
try {
|
|
2398
|
+
while (true) {
|
|
2399
|
+
const { done, value } = await reader.read();
|
|
2400
|
+
if (done) break;
|
|
2401
|
+
onChunk(decoder.decode(value, { stream: true }));
|
|
2402
|
+
}
|
|
2403
|
+
onChunk(decoder.decode());
|
|
2404
|
+
} finally {
|
|
2405
|
+
reader.releaseLock();
|
|
2406
|
+
}
|
|
2407
|
+
} else {
|
|
2408
|
+
for await (const chunk of body) {
|
|
2409
|
+
onChunk(typeof chunk === "string" ? chunk : chunk.toString());
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
__name(pipeStream, "pipeStream");
|
|
2414
|
+
async function relayAnthropicStream(requestBody, config, route, res) {
|
|
2415
|
+
const payload = buildAnthropicPayload(requestBody, true);
|
|
2416
|
+
const baseUrl = route === "minimax" ? `${config.baseUrlAnthropic.replace(/\/+$/, "")}/minimax/v1/messages` : `${config.baseUrlAnthropic.replace(/\/+$/, "")}/v1/messages`;
|
|
2417
|
+
let upstream;
|
|
2418
|
+
try {
|
|
2419
|
+
upstream = await upstreamFetch(baseUrl, {
|
|
2420
|
+
method: "POST",
|
|
2421
|
+
headers: {
|
|
2422
|
+
"content-type": "application/json",
|
|
2423
|
+
"x-api-key": config.apiKey,
|
|
2424
|
+
"anthropic-version": "2023-06-01",
|
|
2425
|
+
"user-agent": "holysheep-openclaw-bridge/1.0"
|
|
2426
|
+
},
|
|
2427
|
+
body: JSON.stringify(payload)
|
|
2428
|
+
});
|
|
2429
|
+
} catch (err) {
|
|
2430
|
+
return sendJson(res, 500, { error: { message: err.message || "Bridge upstream error" } });
|
|
2431
|
+
}
|
|
2432
|
+
if (!upstream.ok) {
|
|
2433
|
+
let errBody;
|
|
2434
|
+
try {
|
|
2435
|
+
errBody = JSON.parse(await upstream.text());
|
|
2436
|
+
} catch {
|
|
2437
|
+
errBody = { error: { message: "Upstream error" } };
|
|
2438
|
+
}
|
|
2439
|
+
return sendJson(res, upstream.status, errBody);
|
|
2440
|
+
}
|
|
2441
|
+
res.writeHead(200, {
|
|
2442
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
2443
|
+
"cache-control": "no-cache, no-transform",
|
|
2444
|
+
connection: "keep-alive"
|
|
2445
|
+
});
|
|
2446
|
+
const msgId = `chatcmpl_${Date.now()}`;
|
|
2447
|
+
const created = Math.floor(Date.now() / 1e3);
|
|
2448
|
+
const model = requestBody.model;
|
|
2449
|
+
let headerSent = false;
|
|
2450
|
+
let inputTokens = 0;
|
|
2451
|
+
const toolBlocks = {};
|
|
2452
|
+
function writeChunk(delta, finishReason, usage) {
|
|
2453
|
+
const chunk = {
|
|
2454
|
+
id: msgId,
|
|
2455
|
+
object: "chat.completion.chunk",
|
|
2456
|
+
created,
|
|
2457
|
+
model,
|
|
2458
|
+
choices: [{ index: 0, delta, finish_reason: finishReason || null }]
|
|
2459
|
+
};
|
|
2460
|
+
if (usage) chunk.usage = usage;
|
|
2461
|
+
res.write(`data: ${JSON.stringify(chunk)}
|
|
2462
|
+
|
|
2463
|
+
`);
|
|
2464
|
+
}
|
|
2465
|
+
__name(writeChunk, "writeChunk");
|
|
2466
|
+
function handleEvent(event, data) {
|
|
2467
|
+
var _a, _b, _c, _d;
|
|
2468
|
+
if (data === "[DONE]") return;
|
|
2469
|
+
let obj;
|
|
2470
|
+
try {
|
|
2471
|
+
obj = JSON.parse(data);
|
|
2472
|
+
} catch {
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
if (event === "message_start") {
|
|
2476
|
+
inputTokens = ((_b = (_a = obj.message) == null ? void 0 : _a.usage) == null ? void 0 : _b.input_tokens) || 0;
|
|
2477
|
+
if (!headerSent) {
|
|
2478
|
+
writeChunk({ role: "assistant", content: "" }, null, null);
|
|
2479
|
+
headerSent = true;
|
|
2480
|
+
}
|
|
2481
|
+
} else if (event === "content_block_start") {
|
|
2482
|
+
if (!headerSent) {
|
|
2483
|
+
writeChunk({ role: "assistant", content: "" }, null, null);
|
|
2484
|
+
headerSent = true;
|
|
2485
|
+
}
|
|
2486
|
+
const block = obj.content_block || {};
|
|
2487
|
+
if (block.type === "tool_use") {
|
|
2488
|
+
toolBlocks[obj.index] = { id: block.id, name: block.name, argsBuf: "" };
|
|
2489
|
+
writeChunk({
|
|
2490
|
+
tool_calls: [{ index: obj.index, id: block.id, type: "function", function: { name: block.name, arguments: "" } }]
|
|
2491
|
+
}, null, null);
|
|
2492
|
+
}
|
|
2493
|
+
} else if (event === "content_block_delta") {
|
|
2494
|
+
if (!headerSent) {
|
|
2495
|
+
writeChunk({ role: "assistant", content: "" }, null, null);
|
|
2496
|
+
headerSent = true;
|
|
2497
|
+
}
|
|
2498
|
+
const delta = obj.delta || {};
|
|
2499
|
+
if (delta.type === "text_delta" && typeof delta.text === "string") {
|
|
2500
|
+
writeChunk({ content: delta.text }, null, null);
|
|
2501
|
+
} else if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
|
|
2502
|
+
if (toolBlocks[obj.index]) toolBlocks[obj.index].argsBuf += delta.partial_json;
|
|
2503
|
+
writeChunk({ tool_calls: [{ index: obj.index, function: { arguments: delta.partial_json } }] }, null, null);
|
|
2504
|
+
}
|
|
2505
|
+
} else if (event === "message_delta") {
|
|
2506
|
+
const stopReason = (_c = obj.delta) == null ? void 0 : _c.stop_reason;
|
|
2507
|
+
const finishReason = mapFinishReason(stopReason);
|
|
2508
|
+
const outputTokens = ((_d = obj.usage) == null ? void 0 : _d.output_tokens) || 0;
|
|
2509
|
+
const usage = {
|
|
2510
|
+
prompt_tokens: inputTokens,
|
|
2511
|
+
completion_tokens: outputTokens,
|
|
2512
|
+
total_tokens: inputTokens + outputTokens
|
|
2513
|
+
};
|
|
2514
|
+
if (!headerSent) {
|
|
2515
|
+
writeChunk({ role: "assistant", content: "" }, null, null);
|
|
2516
|
+
headerSent = true;
|
|
2517
|
+
}
|
|
2518
|
+
writeChunk({}, finishReason, usage);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
__name(handleEvent, "handleEvent");
|
|
2522
|
+
let buf = "";
|
|
2523
|
+
let curEvent = "";
|
|
2524
|
+
function processBuffer(text) {
|
|
2525
|
+
buf += text;
|
|
2526
|
+
const lines = buf.split("\n");
|
|
2527
|
+
buf = lines.pop() ?? "";
|
|
2528
|
+
for (const line of lines) {
|
|
2529
|
+
const trimmed = line.trimEnd();
|
|
2530
|
+
if (trimmed.startsWith("event:")) {
|
|
2531
|
+
curEvent = trimmed.slice(6).trim();
|
|
2532
|
+
} else if (trimmed.startsWith("data:")) {
|
|
2533
|
+
handleEvent(curEvent, trimmed.slice(5).trim());
|
|
2534
|
+
curEvent = "";
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
__name(processBuffer, "processBuffer");
|
|
2539
|
+
try {
|
|
2540
|
+
await pipeStream(upstream.body, processBuffer);
|
|
2541
|
+
} catch {
|
|
2542
|
+
}
|
|
2543
|
+
if (!res.writableEnded) res.end("data: [DONE]\n\n");
|
|
2544
|
+
}
|
|
2545
|
+
__name(relayAnthropicStream, "relayAnthropicStream");
|
|
2546
|
+
async function relayAnthropicRequest(requestBody, config, route, res) {
|
|
2547
|
+
if (requestBody.stream === true) {
|
|
2548
|
+
return relayAnthropicStream(requestBody, config, route, res);
|
|
2549
|
+
}
|
|
2550
|
+
const payload = buildAnthropicPayload(requestBody);
|
|
2551
|
+
const baseUrl = route === "minimax" ? `${config.baseUrlAnthropic.replace(/\/+$/, "")}/minimax/v1/messages` : `${config.baseUrlAnthropic.replace(/\/+$/, "")}/v1/messages`;
|
|
2552
|
+
const upstream = await upstreamFetch(baseUrl, {
|
|
2553
|
+
method: "POST",
|
|
2554
|
+
headers: {
|
|
2555
|
+
"content-type": "application/json",
|
|
2556
|
+
"x-api-key": config.apiKey,
|
|
2557
|
+
"anthropic-version": "2023-06-01",
|
|
2558
|
+
"user-agent": "holysheep-openclaw-bridge/1.0"
|
|
2559
|
+
},
|
|
2560
|
+
body: JSON.stringify(payload)
|
|
2561
|
+
});
|
|
2562
|
+
const text = await upstream.text();
|
|
2563
|
+
let body;
|
|
2564
|
+
try {
|
|
2565
|
+
body = JSON.parse(text);
|
|
2566
|
+
} catch {
|
|
2567
|
+
body = { error: { message: text || "Invalid upstream response" } };
|
|
2568
|
+
}
|
|
2569
|
+
if (!upstream.ok) {
|
|
2570
|
+
return sendJson(res, upstream.status, body);
|
|
2571
|
+
}
|
|
2572
|
+
const openaiBody = anthropicToOpenAIResponse(body, requestBody.model);
|
|
2573
|
+
if (requestBody.stream) return sendOpenAIStream(res, openaiBody);
|
|
2574
|
+
return sendJson(res, 200, openaiBody);
|
|
2575
|
+
}
|
|
2576
|
+
__name(relayAnthropicRequest, "relayAnthropicRequest");
|
|
2577
|
+
function buildModelsResponse(config) {
|
|
2578
|
+
return {
|
|
2579
|
+
object: "list",
|
|
2580
|
+
data: (config.models || []).map((model) => ({
|
|
2581
|
+
id: model,
|
|
2582
|
+
object: "model",
|
|
2583
|
+
owned_by: "holysheep"
|
|
2584
|
+
}))
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2587
|
+
__name(buildModelsResponse, "buildModelsResponse");
|
|
2588
|
+
function isProcessAlive(pid) {
|
|
2589
|
+
if (!Number.isInteger(pid) || pid <= 0) return null;
|
|
2590
|
+
try {
|
|
2591
|
+
process.kill(pid, 0);
|
|
2592
|
+
return true;
|
|
2593
|
+
} catch (error) {
|
|
2594
|
+
if (error && error.code === "EPERM") return true;
|
|
2595
|
+
return false;
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
__name(isProcessAlive, "isProcessAlive");
|
|
2599
|
+
async function checkGatewayHealth(config) {
|
|
2600
|
+
var _a;
|
|
2601
|
+
const gatewayPort = Number(config.gatewayPort);
|
|
2602
|
+
if (!Number.isInteger(gatewayPort) || gatewayPort <= 0) {
|
|
2603
|
+
return { ok: true, reason: "no_gateway_port" };
|
|
2604
|
+
}
|
|
2605
|
+
const gatewayPid = Number(config.gatewayPid);
|
|
2606
|
+
const pidAlive = isProcessAlive(gatewayPid);
|
|
2607
|
+
if (pidAlive === false) {
|
|
2608
|
+
return { ok: false, reason: "gateway_pid_exited" };
|
|
2609
|
+
}
|
|
2610
|
+
const host = config.gatewayHost || "127.0.0.1";
|
|
2611
|
+
const timeout = Number((_a = config.watchdog) == null ? void 0 : _a.requestTimeoutMs) || DEFAULT_WATCHDOG_REQUEST_TIMEOUT_MS;
|
|
2612
|
+
try {
|
|
2613
|
+
const http2 = require("http");
|
|
2614
|
+
await new Promise((resolve, reject) => {
|
|
2615
|
+
const req = http2.get({ hostname: host, port: gatewayPort, path: "/", family: 4 }, resolve);
|
|
2616
|
+
req.setTimeout(timeout, () => {
|
|
2617
|
+
req.destroy();
|
|
2618
|
+
reject(new Error("timeout"));
|
|
2619
|
+
});
|
|
2620
|
+
req.on("error", reject);
|
|
2621
|
+
});
|
|
2622
|
+
return { ok: true, reason: "gateway_http_ok" };
|
|
2623
|
+
} catch {
|
|
2624
|
+
return { ok: false, reason: "gateway_http_unreachable" };
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
__name(checkGatewayHealth, "checkGatewayHealth");
|
|
2628
|
+
function stopBridge(server, reason) {
|
|
2629
|
+
process.stdout.write(`HolySheep OpenClaw bridge stopping: ${reason}
|
|
2630
|
+
`);
|
|
2631
|
+
server.close(() => process.exit(0));
|
|
2632
|
+
setTimeout(() => process.exit(0), 250).unref();
|
|
2633
|
+
}
|
|
2634
|
+
__name(stopBridge, "stopBridge");
|
|
2635
|
+
function startGatewayWatchdog(server, configPath = BRIDGE_CONFIG_FILE) {
|
|
2636
|
+
const bridgeStartedAt = Date.now();
|
|
2637
|
+
let consecutiveFailures = 0;
|
|
2638
|
+
let stopping = false;
|
|
2639
|
+
const timer = setInterval(async () => {
|
|
2640
|
+
if (stopping) return;
|
|
2641
|
+
let config;
|
|
2642
|
+
try {
|
|
2643
|
+
config = readBridgeConfig(configPath);
|
|
2644
|
+
} catch {
|
|
2645
|
+
stopping = true;
|
|
2646
|
+
stopBridge(server, "bridge config missing");
|
|
2647
|
+
return;
|
|
2648
|
+
}
|
|
2649
|
+
const watchdog = config.watchdog || {};
|
|
2650
|
+
if (watchdog.enabled === false) return;
|
|
2651
|
+
const startupGraceMs = Number(watchdog.startupGraceMs) || DEFAULT_WATCHDOG_STARTUP_GRACE_MS;
|
|
2652
|
+
const failureThreshold = Number(watchdog.failureThreshold) || DEFAULT_WATCHDOG_FAILURE_THRESHOLD;
|
|
2653
|
+
const health = await checkGatewayHealth(config);
|
|
2654
|
+
if (health.ok) {
|
|
2655
|
+
consecutiveFailures = 0;
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
const gatewayStartedAt = Date.parse(config.gatewayStartedAt || "") || bridgeStartedAt;
|
|
2659
|
+
if (Date.now() - gatewayStartedAt < startupGraceMs) {
|
|
2660
|
+
return;
|
|
2661
|
+
}
|
|
2662
|
+
consecutiveFailures += 1;
|
|
2663
|
+
if (consecutiveFailures < failureThreshold) return;
|
|
2664
|
+
stopping = true;
|
|
2665
|
+
stopBridge(server, `OpenClaw Gateway unavailable (${health.reason})`);
|
|
2666
|
+
}, DEFAULT_WATCHDOG_INTERVAL_MS);
|
|
2667
|
+
timer.unref();
|
|
2668
|
+
server.on("close", () => clearInterval(timer));
|
|
2669
|
+
}
|
|
2670
|
+
__name(startGatewayWatchdog, "startGatewayWatchdog");
|
|
2671
|
+
function createBridgeServer(configPath = BRIDGE_CONFIG_FILE) {
|
|
2672
|
+
return http.createServer(async (req, res) => {
|
|
2673
|
+
if (req.method === "OPTIONS") {
|
|
2674
|
+
res.writeHead(204, {
|
|
2675
|
+
"access-control-allow-origin": "*",
|
|
2676
|
+
"access-control-allow-methods": "GET,POST,OPTIONS",
|
|
2677
|
+
"access-control-allow-headers": "content-type,authorization,x-api-key,anthropic-version"
|
|
2678
|
+
});
|
|
2679
|
+
return res.end();
|
|
2680
|
+
}
|
|
2681
|
+
try {
|
|
2682
|
+
const config = readBridgeConfig(configPath);
|
|
2683
|
+
const url = new URL(req.url, `http://${req.headers.host || "127.0.0.1"}`);
|
|
2684
|
+
if (req.method === "GET" && url.pathname === "/health") {
|
|
2685
|
+
return sendJson(res, 200, { ok: true, port: config.port, models: config.models || [] });
|
|
2686
|
+
}
|
|
2687
|
+
if (req.method === "GET" && url.pathname === "/v1/models") {
|
|
2688
|
+
return sendJson(res, 200, buildModelsResponse(config));
|
|
2689
|
+
}
|
|
2690
|
+
if (req.method === "POST" && url.pathname === "/v1/chat/completions") {
|
|
2691
|
+
const requestBody = await readJsonBody(req);
|
|
2692
|
+
const route = pickRoute(requestBody.model);
|
|
2693
|
+
if (route === "openai") return relayOpenAIRequest(requestBody, config, res);
|
|
2694
|
+
return relayAnthropicRequest(requestBody, config, route, res);
|
|
2695
|
+
}
|
|
2696
|
+
return sendJson(res, 404, { error: { message: "Not found" } });
|
|
2697
|
+
} catch (error) {
|
|
2698
|
+
return sendJson(res, 500, { error: { message: error.message || "Bridge error" } });
|
|
2699
|
+
}
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
__name(createBridgeServer, "createBridgeServer");
|
|
2703
|
+
function startBridge(args = parseArgs(process.argv.slice(2))) {
|
|
2704
|
+
const config = readBridgeConfig(args.config);
|
|
2705
|
+
const port = args.port || config.port;
|
|
2706
|
+
const host = args.host || "127.0.0.1";
|
|
2707
|
+
const server = createBridgeServer(args.config);
|
|
2708
|
+
server.listen(port, host, () => {
|
|
2709
|
+
process.stdout.write(`HolySheep OpenClaw bridge listening on http://${host}:${port}
|
|
2710
|
+
`);
|
|
2711
|
+
});
|
|
2712
|
+
startGatewayWatchdog(server, args.config);
|
|
2713
|
+
return server;
|
|
2714
|
+
}
|
|
2715
|
+
__name(startBridge, "startBridge");
|
|
2716
|
+
if (require.main === module2) {
|
|
2717
|
+
startBridge();
|
|
2718
|
+
}
|
|
2719
|
+
module2.exports = {
|
|
2720
|
+
BRIDGE_CONFIG_FILE,
|
|
2721
|
+
buildAnthropicPayload,
|
|
2722
|
+
anthropicToOpenAIResponse,
|
|
2723
|
+
buildModelsResponse,
|
|
2724
|
+
createBridgeServer,
|
|
2725
|
+
parseArgs,
|
|
2726
|
+
parseOpenAIStreamText,
|
|
2727
|
+
pickRoute,
|
|
2728
|
+
readBridgeConfig,
|
|
2729
|
+
startBridge
|
|
2730
|
+
};
|
|
2731
|
+
}
|
|
2732
|
+
});
|
|
2733
|
+
|
|
2734
|
+
// src/utils/paths.js
|
|
2735
|
+
var require_paths = __commonJS({
|
|
2736
|
+
"src/utils/paths.js"(exports2, module2) {
|
|
2737
|
+
"use strict";
|
|
2738
|
+
var path = require("path");
|
|
2739
|
+
var fs = require("fs");
|
|
2740
|
+
function locatePackageRoot() {
|
|
2741
|
+
const main = require.main && require.main.filename || __filename;
|
|
2742
|
+
let dir = path.dirname(main);
|
|
2743
|
+
for (let i = 0; i < 6; i++) {
|
|
2744
|
+
try {
|
|
2745
|
+
const pkgPath = path.join(dir, "package.json");
|
|
2746
|
+
if (fs.existsSync(pkgPath)) {
|
|
2747
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
2748
|
+
if (pkg && pkg.name === "@simonyea/holysheep-cli") return dir;
|
|
2749
|
+
}
|
|
2750
|
+
} catch {
|
|
2751
|
+
}
|
|
2752
|
+
const parent = path.dirname(dir);
|
|
2753
|
+
if (parent === dir) break;
|
|
2754
|
+
dir = parent;
|
|
2755
|
+
}
|
|
2756
|
+
return path.resolve(path.dirname(main), "..");
|
|
2757
|
+
}
|
|
2758
|
+
__name(locatePackageRoot, "locatePackageRoot");
|
|
2759
|
+
var PACKAGE_ROOT = locatePackageRoot();
|
|
2760
|
+
var MAIN_FILE = require.main && require.main.filename || __filename;
|
|
2761
|
+
var IS_BUNDLED = /[/\\]dist[/\\][^/\\]+\.js$/i.test(MAIN_FILE);
|
|
2762
|
+
var RUNTIME_DIR = IS_BUNDLED ? path.join(PACKAGE_ROOT, "dist") : path.join(PACKAGE_ROOT, "src");
|
|
2763
|
+
function selfEntrypoint() {
|
|
2764
|
+
return IS_BUNDLED ? path.join(RUNTIME_DIR, "index.js") : path.join(RUNTIME_DIR, "index.js");
|
|
2765
|
+
}
|
|
2766
|
+
__name(selfEntrypoint, "selfEntrypoint");
|
|
2767
|
+
function configureWorkerPath() {
|
|
2768
|
+
return IS_BUNDLED ? path.join(RUNTIME_DIR, "configure-worker.js") : path.join(PACKAGE_ROOT, "src", "webui", "configure-worker.js");
|
|
2769
|
+
}
|
|
2770
|
+
__name(configureWorkerPath, "configureWorkerPath");
|
|
2771
|
+
function legacyIndexHtmlPath() {
|
|
2772
|
+
return IS_BUNDLED ? path.join(RUNTIME_DIR, "index.html") : path.join(PACKAGE_ROOT, "src", "webui", "index.html");
|
|
2773
|
+
}
|
|
2774
|
+
__name(legacyIndexHtmlPath, "legacyIndexHtmlPath");
|
|
2775
|
+
function processProxyInjectPath() {
|
|
2776
|
+
return IS_BUNDLED ? path.join(RUNTIME_DIR, "process-proxy-inject.js") : path.join(PACKAGE_ROOT, "src", "tools", "process-proxy-inject.js");
|
|
2777
|
+
}
|
|
2778
|
+
__name(processProxyInjectPath, "processProxyInjectPath");
|
|
2779
|
+
function ptyHermesWrapperPath() {
|
|
2780
|
+
return IS_BUNDLED ? path.join(RUNTIME_DIR, "pty-hermes-wrapper.py") : path.join(PACKAGE_ROOT, "src", "tools", "pty-hermes-wrapper.py");
|
|
2781
|
+
}
|
|
2782
|
+
__name(ptyHermesWrapperPath, "ptyHermesWrapperPath");
|
|
2783
|
+
function devAionuiForkDir() {
|
|
2784
|
+
return IS_BUNDLED ? null : path.join(PACKAGE_ROOT, "aionui-fork");
|
|
2785
|
+
}
|
|
2786
|
+
__name(devAionuiForkDir, "devAionuiForkDir");
|
|
2787
|
+
function cliVersion() {
|
|
2788
|
+
try {
|
|
2789
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
2790
|
+
return pkg.version;
|
|
2791
|
+
} catch {
|
|
2792
|
+
return "unknown";
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
__name(cliVersion, "cliVersion");
|
|
2796
|
+
module2.exports = {
|
|
2797
|
+
PACKAGE_ROOT,
|
|
2798
|
+
IS_BUNDLED,
|
|
2799
|
+
RUNTIME_DIR,
|
|
2800
|
+
selfEntrypoint,
|
|
2801
|
+
configureWorkerPath,
|
|
2802
|
+
legacyIndexHtmlPath,
|
|
2803
|
+
processProxyInjectPath,
|
|
2804
|
+
ptyHermesWrapperPath,
|
|
2805
|
+
devAionuiForkDir,
|
|
2806
|
+
cliVersion
|
|
2807
|
+
};
|
|
2808
|
+
}
|
|
2809
|
+
});
|
|
2810
|
+
|
|
2811
|
+
// src/tools/openclaw.js
|
|
2812
|
+
var require_openclaw = __commonJS({
|
|
2813
|
+
"src/tools/openclaw.js"(exports2, module2) {
|
|
2814
|
+
var fs = require("fs");
|
|
2815
|
+
var path = require("path");
|
|
2816
|
+
var os = require("os");
|
|
2817
|
+
var { spawnSync, spawn, execSync } = require("child_process");
|
|
2818
|
+
var { commandExists } = require_which();
|
|
2819
|
+
var { BRIDGE_CONFIG_FILE } = require_openclaw_bridge();
|
|
2820
|
+
var OPENCLAW_DIR = path.join(os.homedir(), ".openclaw");
|
|
2821
|
+
var CONFIG_FILE = path.join(OPENCLAW_DIR, "openclaw.json");
|
|
2822
|
+
var OPENCLAW_LAUNCH_AGENTS_DIR = path.join(os.homedir(), "Library", "LaunchAgents");
|
|
2823
|
+
var OPENCLAW_GATEWAY_PLIST = path.join(OPENCLAW_LAUNCH_AGENTS_DIR, "ai.openclaw.gateway.plist");
|
|
2824
|
+
var isWin = process.platform === "win32";
|
|
2825
|
+
var DEFAULT_BRIDGE_PORT = 18788;
|
|
2826
|
+
var DEFAULT_GATEWAY_PORT = 18789;
|
|
2827
|
+
var MAX_PORT_SCAN = 40;
|
|
2828
|
+
var OPENCLAW_DEFAULT_MODEL = "gpt-5.4";
|
|
2829
|
+
var OPENCLAW_DEFAULT_CODEX_SPARK_MODEL = "gpt-5.3-codex-spark";
|
|
2830
|
+
var OPENCLAW_DEFAULT_CLAUDE_MODEL = "claude-sonnet-4-6";
|
|
2831
|
+
var OPENCLAW_DEFAULT_MINIMAX_MODEL = "MiniMax-M2.7-highspeed";
|
|
2832
|
+
var OPENCLAW_PROVIDER_NAME = "holysheep";
|
|
2833
|
+
function atomicWriteJson(filePath, data) {
|
|
2834
|
+
const dir = path.dirname(filePath);
|
|
2835
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2836
|
+
const body = JSON.stringify(data, null, 2);
|
|
2837
|
+
const tmp = `${filePath}.tmp.${process.pid}.${Math.random().toString(36).slice(2, 10)}`;
|
|
2838
|
+
fs.writeFileSync(tmp, body, "utf8");
|
|
2839
|
+
try {
|
|
2840
|
+
fs.renameSync(tmp, filePath);
|
|
2841
|
+
return;
|
|
2842
|
+
} catch (renameErr) {
|
|
2843
|
+
if (process.platform === "win32" || renameErr.code === "EXDEV" || renameErr.code === "EEXIST") {
|
|
2844
|
+
try {
|
|
2845
|
+
fs.copyFileSync(tmp, filePath);
|
|
2846
|
+
try {
|
|
2847
|
+
fs.unlinkSync(tmp);
|
|
2848
|
+
} catch {
|
|
2849
|
+
}
|
|
2850
|
+
return;
|
|
2851
|
+
} catch (copyErr) {
|
|
2852
|
+
try {
|
|
2853
|
+
fs.unlinkSync(tmp);
|
|
2854
|
+
} catch {
|
|
2855
|
+
}
|
|
2856
|
+
throw copyErr;
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
try {
|
|
2860
|
+
fs.unlinkSync(tmp);
|
|
2861
|
+
} catch {
|
|
2862
|
+
}
|
|
2863
|
+
throw renameErr;
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
__name(atomicWriteJson, "atomicWriteJson");
|
|
2867
|
+
function pruneClobberedBackups(maxAgeMs = 7 * 24 * 3600 * 1e3) {
|
|
2868
|
+
try {
|
|
2869
|
+
if (!fs.existsSync(OPENCLAW_DIR)) return { scanned: 0, removed: 0 };
|
|
2870
|
+
const entries = fs.readdirSync(OPENCLAW_DIR);
|
|
2871
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
2872
|
+
let scanned = 0;
|
|
2873
|
+
let removed = 0;
|
|
2874
|
+
for (const name of entries) {
|
|
2875
|
+
if (!/^openclaw\.json\.clobbered\./.test(name)) continue;
|
|
2876
|
+
scanned++;
|
|
2877
|
+
const abs = path.join(OPENCLAW_DIR, name);
|
|
2878
|
+
try {
|
|
2879
|
+
const st = fs.statSync(abs);
|
|
2880
|
+
if (st.mtimeMs < cutoff) {
|
|
2881
|
+
fs.unlinkSync(abs);
|
|
2882
|
+
removed++;
|
|
2883
|
+
}
|
|
2884
|
+
} catch {
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
return { scanned, removed };
|
|
2888
|
+
} catch {
|
|
2889
|
+
return { scanned: 0, removed: 0 };
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
__name(pruneClobberedBackups, "pruneClobberedBackups");
|
|
2893
|
+
function getOpenClawBinaryCandidates() {
|
|
2894
|
+
return isWin ? ["openclaw.cmd", "openclaw"] : ["openclaw"];
|
|
2895
|
+
}
|
|
2896
|
+
__name(getOpenClawBinaryCandidates, "getOpenClawBinaryCandidates");
|
|
2897
|
+
function getBinaryRunner() {
|
|
2898
|
+
return isWin ? { cmd: "openclaw.cmd", argsPrefix: [], shell: true, label: "openclaw", via: "binary" } : { cmd: "openclaw", argsPrefix: [], shell: false, label: "openclaw", via: "binary" };
|
|
2899
|
+
}
|
|
2900
|
+
__name(getBinaryRunner, "getBinaryRunner");
|
|
2901
|
+
function hasOpenClawBinary() {
|
|
2902
|
+
return getOpenClawBinaryCandidates().some((cmd) => commandExists(cmd));
|
|
2903
|
+
}
|
|
2904
|
+
__name(hasOpenClawBinary, "hasOpenClawBinary");
|
|
2905
|
+
function hasNpx() {
|
|
2906
|
+
return commandExists("npx");
|
|
2907
|
+
}
|
|
2908
|
+
__name(hasNpx, "hasNpx");
|
|
2909
|
+
function getRunner(preferNpx = false) {
|
|
2910
|
+
const binaryRunner = hasOpenClawBinary() ? getBinaryRunner() : null;
|
|
2911
|
+
if (!preferNpx && hasOpenClawBinary()) {
|
|
2912
|
+
return binaryRunner;
|
|
2913
|
+
}
|
|
2914
|
+
if (hasNpx()) {
|
|
2915
|
+
return { cmd: "npx", argsPrefix: ["openclaw"], shell: isWin, label: "npx openclaw", via: "npx" };
|
|
2916
|
+
}
|
|
2917
|
+
if (binaryRunner) {
|
|
2918
|
+
return binaryRunner;
|
|
2919
|
+
}
|
|
2920
|
+
return null;
|
|
2921
|
+
}
|
|
2922
|
+
__name(getRunner, "getRunner");
|
|
2923
|
+
function runWithRunner(runner, args, opts = {}) {
|
|
2924
|
+
return spawnSync(runner.cmd, [...runner.argsPrefix, ...args], {
|
|
2925
|
+
shell: runner.shell,
|
|
2926
|
+
timeout: opts.timeout || 3e4,
|
|
2927
|
+
stdio: opts.stdio || "pipe",
|
|
2928
|
+
encoding: "utf8",
|
|
2929
|
+
windowsHide: true
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
__name(runWithRunner, "runWithRunner");
|
|
2933
|
+
function normalizeVersionOutput(text) {
|
|
2934
|
+
return firstLine(text).replace(/^openclaw\s+/i, "").trim();
|
|
2935
|
+
}
|
|
2936
|
+
__name(normalizeVersionOutput, "normalizeVersionOutput");
|
|
2937
|
+
function probeRunner(runner, timeout) {
|
|
2938
|
+
const result = runWithRunner(runner, ["--version"], { timeout });
|
|
2939
|
+
if (result.error || result.status !== 0) return null;
|
|
2940
|
+
const version = normalizeVersionOutput(result.stdout || result.stderr || "");
|
|
2941
|
+
return version || null;
|
|
2942
|
+
}
|
|
2943
|
+
__name(probeRunner, "probeRunner");
|
|
2944
|
+
function runOpenClaw(args, opts = {}) {
|
|
2945
|
+
const runner = getRunner(Boolean(opts.preferNpx));
|
|
2946
|
+
if (!runner) {
|
|
2947
|
+
return { status: 1, stdout: "", stderr: "OpenClaw CLI not found" };
|
|
2948
|
+
}
|
|
2949
|
+
return runWithRunner(runner, args, opts);
|
|
2950
|
+
}
|
|
2951
|
+
__name(runOpenClaw, "runOpenClaw");
|
|
2952
|
+
function spawnOpenClaw(args, opts = {}) {
|
|
2953
|
+
const runner = getRunner(Boolean(opts.preferNpx));
|
|
2954
|
+
if (!runner) throw new Error("OpenClaw CLI not found");
|
|
2955
|
+
const { preferNpx: _preferNpx, ...spawnOpts } = opts;
|
|
2956
|
+
return spawn(runner.cmd, [...runner.argsPrefix, ...args], {
|
|
2957
|
+
shell: runner.shell,
|
|
2958
|
+
windowsHide: true,
|
|
2959
|
+
...spawnOpts
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
__name(spawnOpenClaw, "spawnOpenClaw");
|
|
2963
|
+
function getPreferredRuntime() {
|
|
2964
|
+
return module2.exports._useNpx || !hasOpenClawBinary();
|
|
2965
|
+
}
|
|
2966
|
+
__name(getPreferredRuntime, "getPreferredRuntime");
|
|
2967
|
+
function firstLine(text) {
|
|
2968
|
+
return String(text || "").trim().split("\n")[0] || "";
|
|
2969
|
+
}
|
|
2970
|
+
__name(firstLine, "firstLine");
|
|
2971
|
+
function detectRuntime() {
|
|
2972
|
+
const preferNpx = getPreferredRuntime();
|
|
2973
|
+
const runnerOrder = preferNpx ? [getRunner(true), getRunner(false)] : [getRunner(false), getRunner(true)];
|
|
2974
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2975
|
+
for (const runner of runnerOrder) {
|
|
2976
|
+
if (!runner) continue;
|
|
2977
|
+
const key = `${runner.via}:${runner.cmd}:${runner.argsPrefix.join(" ")}`;
|
|
2978
|
+
if (seen.has(key)) continue;
|
|
2979
|
+
seen.add(key);
|
|
2980
|
+
const version = probeRunner(runner, runner.via === "npx" ? 6e4 : 15e3);
|
|
2981
|
+
if (version) {
|
|
2982
|
+
return {
|
|
2983
|
+
available: true,
|
|
2984
|
+
via: runner.via,
|
|
2985
|
+
command: runner.label,
|
|
2986
|
+
version
|
|
2987
|
+
};
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
const fallbackRunner = getRunner(preferNpx);
|
|
2991
|
+
if (fallbackRunner) {
|
|
2992
|
+
return {
|
|
2993
|
+
available: false,
|
|
2994
|
+
via: fallbackRunner.via,
|
|
2995
|
+
command: fallbackRunner.label,
|
|
2996
|
+
version: null
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
return { available: false, via: null, command: null, version: null };
|
|
3000
|
+
}
|
|
3001
|
+
__name(detectRuntime, "detectRuntime");
|
|
3002
|
+
function readBridgeConfig() {
|
|
3003
|
+
try {
|
|
3004
|
+
if (fs.existsSync(BRIDGE_CONFIG_FILE)) {
|
|
3005
|
+
return JSON.parse(fs.readFileSync(BRIDGE_CONFIG_FILE, "utf8"));
|
|
3006
|
+
}
|
|
3007
|
+
} catch {
|
|
3008
|
+
}
|
|
3009
|
+
return {};
|
|
3010
|
+
}
|
|
3011
|
+
__name(readBridgeConfig, "readBridgeConfig");
|
|
3012
|
+
function writeBridgeConfig(data) {
|
|
3013
|
+
atomicWriteJson(BRIDGE_CONFIG_FILE, data);
|
|
3014
|
+
}
|
|
3015
|
+
__name(writeBridgeConfig, "writeBridgeConfig");
|
|
3016
|
+
function updateBridgeConfig(patch) {
|
|
3017
|
+
const current = readBridgeConfig();
|
|
3018
|
+
writeBridgeConfig({
|
|
3019
|
+
...current,
|
|
3020
|
+
...patch
|
|
3021
|
+
});
|
|
3022
|
+
}
|
|
3023
|
+
__name(updateBridgeConfig, "updateBridgeConfig");
|
|
3024
|
+
function getConfiguredBridgePort(config = readBridgeConfig()) {
|
|
3025
|
+
const port = Number(config == null ? void 0 : config.port);
|
|
3026
|
+
return Number.isInteger(port) && port > 0 ? port : DEFAULT_BRIDGE_PORT;
|
|
3027
|
+
}
|
|
3028
|
+
__name(getConfiguredBridgePort, "getConfiguredBridgePort");
|
|
3029
|
+
function getBridgeBaseUrl(port = getConfiguredBridgePort()) {
|
|
3030
|
+
return `http://127.0.0.1:${port}/v1`;
|
|
3031
|
+
}
|
|
3032
|
+
__name(getBridgeBaseUrl, "getBridgeBaseUrl");
|
|
3033
|
+
function waitForBridge(port) {
|
|
3034
|
+
for (let i = 0; i < 10; i++) {
|
|
3035
|
+
const t0 = Date.now();
|
|
3036
|
+
while (Date.now() - t0 < 500) {
|
|
3037
|
+
}
|
|
3038
|
+
try {
|
|
3039
|
+
execSync(
|
|
3040
|
+
isWin ? `powershell -NonInteractive -Command "try{(Invoke-WebRequest -Uri http://127.0.0.1:${port}/health -TimeoutSec 1 -UseBasicParsing).StatusCode}catch{exit 1}"` : `curl -sf http://127.0.0.1:${port}/health -o /dev/null --max-time 1`,
|
|
3041
|
+
{ stdio: "ignore", timeout: 3e3 }
|
|
3042
|
+
);
|
|
3043
|
+
return true;
|
|
3044
|
+
} catch {
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
return false;
|
|
3048
|
+
}
|
|
3049
|
+
__name(waitForBridge, "waitForBridge");
|
|
3050
|
+
function stopBridge() {
|
|
3051
|
+
try {
|
|
3052
|
+
if (isWin) {
|
|
3053
|
+
execSync('taskkill /F /FI "WINDOWTITLE eq openclaw-bridge*" 2>nul', { shell: true, stdio: "ignore" });
|
|
3054
|
+
const out = execSync(`wmic process where "commandline like '%openclaw-bridge%'" get processid`, { shell: true, stdio: "pipe", encoding: "utf8" });
|
|
3055
|
+
const pids = out.match(/\d+/g);
|
|
3056
|
+
if (pids) pids.forEach((pid) => {
|
|
3057
|
+
try {
|
|
3058
|
+
execSync(`taskkill /F /PID ${pid}`, { stdio: "ignore" });
|
|
3059
|
+
} catch {
|
|
3060
|
+
}
|
|
3061
|
+
});
|
|
3062
|
+
} else {
|
|
3063
|
+
execSync("pkill -f 'openclaw-bridge' 2>/dev/null || true", { shell: true, stdio: "ignore" });
|
|
3064
|
+
}
|
|
3065
|
+
} catch {
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
__name(stopBridge, "stopBridge");
|
|
3069
|
+
function startBridge(port) {
|
|
3070
|
+
if (waitForBridge(port)) return true;
|
|
3071
|
+
const { selfEntrypoint } = require_paths();
|
|
3072
|
+
const scriptPath = selfEntrypoint();
|
|
3073
|
+
const spawnCmd = isWin ? "node" : process.execPath;
|
|
3074
|
+
const spawnOpts = { detached: true, stdio: "ignore", windowsHide: true };
|
|
3075
|
+
const child = spawn(spawnCmd, [scriptPath, "openclaw-bridge", "--port", String(port)], spawnOpts);
|
|
3076
|
+
child.unref();
|
|
3077
|
+
return waitForBridge(port);
|
|
3078
|
+
}
|
|
3079
|
+
__name(startBridge, "startBridge");
|
|
3080
|
+
function getBridgeCommand(port = getConfiguredBridgePort()) {
|
|
3081
|
+
return `hs openclaw-bridge --port ${port}`;
|
|
3082
|
+
}
|
|
3083
|
+
__name(getBridgeCommand, "getBridgeCommand");
|
|
3084
|
+
function pickPrimaryModel(primaryModel, selectedModels) {
|
|
3085
|
+
const models = Array.isArray(selectedModels) ? selectedModels : [];
|
|
3086
|
+
return primaryModel || models[0] || OPENCLAW_DEFAULT_MODEL;
|
|
3087
|
+
}
|
|
3088
|
+
__name(pickPrimaryModel, "pickPrimaryModel");
|
|
3089
|
+
function readConfig() {
|
|
3090
|
+
try {
|
|
3091
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
3092
|
+
const raw = fs.readFileSync(CONFIG_FILE, "utf8");
|
|
3093
|
+
try {
|
|
3094
|
+
return JSON.parse(raw);
|
|
3095
|
+
} catch {
|
|
3096
|
+
return JSON.parse(raw.replace(/^\s*\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, ""));
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
} catch {
|
|
3100
|
+
}
|
|
3101
|
+
return {};
|
|
3102
|
+
}
|
|
3103
|
+
__name(readConfig, "readConfig");
|
|
3104
|
+
function getConfiguredGatewayPort(config = readConfig()) {
|
|
3105
|
+
var _a;
|
|
3106
|
+
const port = Number((_a = config == null ? void 0 : config.gateway) == null ? void 0 : _a.port);
|
|
3107
|
+
return Number.isInteger(port) && port > 0 ? port : DEFAULT_GATEWAY_PORT;
|
|
3108
|
+
}
|
|
3109
|
+
__name(getConfiguredGatewayPort, "getConfiguredGatewayPort");
|
|
3110
|
+
function getConfiguredPrimaryModel(config = readConfig()) {
|
|
3111
|
+
var _a, _b, _c;
|
|
3112
|
+
return ((_c = (_b = (_a = config == null ? void 0 : config.agents) == null ? void 0 : _a.defaults) == null ? void 0 : _b.model) == null ? void 0 : _c.primary) || "";
|
|
3113
|
+
}
|
|
3114
|
+
__name(getConfiguredPrimaryModel, "getConfiguredPrimaryModel");
|
|
3115
|
+
function normalizePrimaryModelRef(ref = getConfiguredPrimaryModel()) {
|
|
3116
|
+
const value = String(ref || "");
|
|
3117
|
+
const parts = value.split("/");
|
|
3118
|
+
return parts[parts.length - 1] || "";
|
|
3119
|
+
}
|
|
3120
|
+
__name(normalizePrimaryModelRef, "normalizePrimaryModelRef");
|
|
3121
|
+
function getPrimaryModelRoute(modelRef = getConfiguredPrimaryModel()) {
|
|
3122
|
+
const model = normalizePrimaryModelRef(modelRef);
|
|
3123
|
+
if (model.startsWith("gpt-")) {
|
|
3124
|
+
return "openai /chat/completions";
|
|
3125
|
+
}
|
|
3126
|
+
if (model.startsWith("claude-")) {
|
|
3127
|
+
return "anthropic /v1/messages";
|
|
3128
|
+
}
|
|
3129
|
+
if (model.startsWith("MiniMax-")) {
|
|
3130
|
+
return "minimax /minimax/v1/messages";
|
|
3131
|
+
}
|
|
3132
|
+
return "unknown";
|
|
3133
|
+
}
|
|
3134
|
+
__name(getPrimaryModelRoute, "getPrimaryModelRoute");
|
|
3135
|
+
function isPortInUse(port) {
|
|
3136
|
+
try {
|
|
3137
|
+
if (isWin) {
|
|
3138
|
+
const out = execSync(`netstat -ano | findstr :${port}`, { shell: true, stdio: "pipe", encoding: "utf8" });
|
|
3139
|
+
return out.trim().length > 0;
|
|
3140
|
+
}
|
|
3141
|
+
execSync(`lsof -nP -iTCP:${port} -sTCP:LISTEN`, { shell: true, stdio: "ignore" });
|
|
3142
|
+
return true;
|
|
3143
|
+
} catch {
|
|
3144
|
+
return false;
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
__name(isPortInUse, "isPortInUse");
|
|
3148
|
+
function listPortListeners(port) {
|
|
3149
|
+
try {
|
|
3150
|
+
if (isWin) {
|
|
3151
|
+
const out2 = execSync(`netstat -ano | findstr :${port}`, { shell: true, stdio: "pipe", encoding: "utf8" });
|
|
3152
|
+
return out2.trim().split("\n").filter(Boolean).map((line) => {
|
|
3153
|
+
const parts = line.trim().split(/\s+/);
|
|
3154
|
+
return { pid: parts[parts.length - 1], command: "pid", detail: parts[1] || "" };
|
|
3155
|
+
});
|
|
3156
|
+
}
|
|
3157
|
+
const out = execSync(`lsof -nP -iTCP:${port} -sTCP:LISTEN`, { shell: true, stdio: "pipe", encoding: "utf8" });
|
|
3158
|
+
return out.trim().split("\n").slice(1).filter(Boolean).map((line) => {
|
|
3159
|
+
const parts = line.trim().split(/\s+/);
|
|
3160
|
+
return {
|
|
3161
|
+
command: parts[0] || "unknown",
|
|
3162
|
+
pid: parts[1] || "?",
|
|
3163
|
+
detail: parts[parts.length - 1] || ""
|
|
3164
|
+
};
|
|
3165
|
+
});
|
|
3166
|
+
} catch {
|
|
3167
|
+
return [];
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
__name(listPortListeners, "listPortListeners");
|
|
3171
|
+
function findAvailableGatewayPort(startPort = DEFAULT_GATEWAY_PORT, excludedPorts = []) {
|
|
3172
|
+
const excluded = new Set((excludedPorts || []).map((value) => Number(value)).filter((value) => Number.isInteger(value) && value > 0));
|
|
3173
|
+
for (let offset = 0; offset < MAX_PORT_SCAN; offset++) {
|
|
3174
|
+
const port = startPort + offset;
|
|
3175
|
+
if (excluded.has(port)) continue;
|
|
3176
|
+
if (!isPortInUse(port)) return port;
|
|
3177
|
+
}
|
|
3178
|
+
return null;
|
|
3179
|
+
}
|
|
3180
|
+
__name(findAvailableGatewayPort, "findAvailableGatewayPort");
|
|
3181
|
+
function getLaunchCommand(port = getConfiguredGatewayPort()) {
|
|
3182
|
+
const runtime = module2.exports._lastRuntimeCommand || (hasOpenClawBinary() ? "openclaw" : "npx openclaw");
|
|
3183
|
+
return `${runtime} gateway --port ${port}`;
|
|
3184
|
+
}
|
|
3185
|
+
__name(getLaunchCommand, "getLaunchCommand");
|
|
3186
|
+
function getDashboardCommand() {
|
|
3187
|
+
const runtime = module2.exports._lastRuntimeCommand || (hasOpenClawBinary() ? "openclaw" : "npx openclaw");
|
|
3188
|
+
return `${runtime} dashboard --no-open`;
|
|
3189
|
+
}
|
|
3190
|
+
__name(getDashboardCommand, "getDashboardCommand");
|
|
3191
|
+
function getDashboardUrlForPort(port) {
|
|
3192
|
+
return `http://127.0.0.1:${port}/`;
|
|
3193
|
+
}
|
|
3194
|
+
__name(getDashboardUrlForPort, "getDashboardUrlForPort");
|
|
3195
|
+
function isNpxCachePath(value) {
|
|
3196
|
+
return /[\\/]_npx[\\/]/i.test(String(value || ""));
|
|
3197
|
+
}
|
|
3198
|
+
__name(isNpxCachePath, "isNpxCachePath");
|
|
3199
|
+
function readOpenClawLaunchAgent() {
|
|
3200
|
+
if (process.platform !== "darwin") return null;
|
|
3201
|
+
try {
|
|
3202
|
+
if (!fs.existsSync(OPENCLAW_GATEWAY_PLIST)) return null;
|
|
3203
|
+
return fs.readFileSync(OPENCLAW_GATEWAY_PLIST, "utf8");
|
|
3204
|
+
} catch {
|
|
3205
|
+
return null;
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
__name(readOpenClawLaunchAgent, "readOpenClawLaunchAgent");
|
|
3209
|
+
function parseLaunchAgentProgramArguments(content) {
|
|
3210
|
+
const match = String(content || "").match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/i);
|
|
3211
|
+
if (!match) return [];
|
|
3212
|
+
return Array.from(match[1].matchAll(/<string>([\s\S]*?)<\/string>/g)).map((item) => item[1]);
|
|
3213
|
+
}
|
|
3214
|
+
__name(parseLaunchAgentProgramArguments, "parseLaunchAgentProgramArguments");
|
|
3215
|
+
function getLaunchAgentDiagnosis() {
|
|
3216
|
+
const content = readOpenClawLaunchAgent();
|
|
3217
|
+
if (!content) {
|
|
3218
|
+
return { exists: false, path: OPENCLAW_GATEWAY_PLIST, unstable: false, programArguments: [] };
|
|
3219
|
+
}
|
|
3220
|
+
const programArguments = parseLaunchAgentProgramArguments(content);
|
|
3221
|
+
const unstableArg = programArguments.find(isNpxCachePath) || "";
|
|
3222
|
+
return {
|
|
3223
|
+
exists: true,
|
|
3224
|
+
path: OPENCLAW_GATEWAY_PLIST,
|
|
3225
|
+
unstable: Boolean(unstableArg),
|
|
3226
|
+
unstableArg,
|
|
3227
|
+
programArguments
|
|
3228
|
+
};
|
|
3229
|
+
}
|
|
3230
|
+
__name(getLaunchAgentDiagnosis, "getLaunchAgentDiagnosis");
|
|
3231
|
+
function removeBrokenLaunchAgent() {
|
|
3232
|
+
const diagnosis = getLaunchAgentDiagnosis();
|
|
3233
|
+
if (!diagnosis.exists || !diagnosis.unstable) return false;
|
|
3234
|
+
try {
|
|
3235
|
+
execSync(`launchctl bootout "gui/${process.getuid()}" "${diagnosis.path}"`, {
|
|
3236
|
+
shell: true,
|
|
3237
|
+
stdio: "ignore"
|
|
3238
|
+
});
|
|
3239
|
+
} catch {
|
|
3240
|
+
}
|
|
3241
|
+
try {
|
|
3242
|
+
fs.unlinkSync(diagnosis.path);
|
|
3243
|
+
return true;
|
|
3244
|
+
} catch {
|
|
3245
|
+
return false;
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
__name(removeBrokenLaunchAgent, "removeBrokenLaunchAgent");
|
|
3249
|
+
function buildModelEntry(id) {
|
|
3250
|
+
return {
|
|
3251
|
+
id,
|
|
3252
|
+
name: `${id} (HolySheep)`,
|
|
3253
|
+
reasoning: false,
|
|
3254
|
+
input: ["text"],
|
|
3255
|
+
contextWindow: 2e5,
|
|
3256
|
+
maxTokens: id.startsWith("gpt-") ? 8192 : 16e3
|
|
3257
|
+
};
|
|
3258
|
+
}
|
|
3259
|
+
__name(buildModelEntry, "buildModelEntry");
|
|
3260
|
+
function normalizeRequestedModels(selectedModels) {
|
|
3261
|
+
const requestedModels = Array.isArray(selectedModels) && selectedModels.length > 0 ? [...selectedModels] : [OPENCLAW_DEFAULT_MODEL, OPENCLAW_DEFAULT_CODEX_SPARK_MODEL, OPENCLAW_DEFAULT_CLAUDE_MODEL, OPENCLAW_DEFAULT_MINIMAX_MODEL];
|
|
3262
|
+
if (!requestedModels.includes(OPENCLAW_DEFAULT_MODEL)) requestedModels.unshift(OPENCLAW_DEFAULT_MODEL);
|
|
3263
|
+
return Array.from(new Set(requestedModels));
|
|
3264
|
+
}
|
|
3265
|
+
__name(normalizeRequestedModels, "normalizeRequestedModels");
|
|
3266
|
+
function buildManagedPlan(baseUrlBridge, apiKey, primaryModel, selectedModels) {
|
|
3267
|
+
const requestedModels = normalizeRequestedModels(selectedModels);
|
|
3268
|
+
const managedModelRefs = requestedModels.map((model) => `${OPENCLAW_PROVIDER_NAME}/${model}`);
|
|
3269
|
+
const fallbackPrimaryModel = pickPrimaryModel(primaryModel, requestedModels);
|
|
3270
|
+
const primaryRef = managedModelRefs.includes(`${OPENCLAW_PROVIDER_NAME}/${fallbackPrimaryModel}`) ? `${OPENCLAW_PROVIDER_NAME}/${fallbackPrimaryModel}` : managedModelRefs[0] || `${OPENCLAW_PROVIDER_NAME}/${OPENCLAW_DEFAULT_MODEL}`;
|
|
3271
|
+
return {
|
|
3272
|
+
providers: {
|
|
3273
|
+
[OPENCLAW_PROVIDER_NAME]: {
|
|
3274
|
+
baseUrl: baseUrlBridge,
|
|
3275
|
+
apiKey,
|
|
3276
|
+
api: "openai-completions",
|
|
3277
|
+
models: requestedModels.map(buildModelEntry)
|
|
3278
|
+
}
|
|
3279
|
+
},
|
|
3280
|
+
managedModelRefs,
|
|
3281
|
+
models: requestedModels,
|
|
3282
|
+
primaryRef
|
|
3283
|
+
};
|
|
3284
|
+
}
|
|
3285
|
+
__name(buildManagedPlan, "buildManagedPlan");
|
|
3286
|
+
function isHolySheepProvider(provider) {
|
|
3287
|
+
return typeof (provider == null ? void 0 : provider.baseUrl) === "string" && (provider.baseUrl.includes("api.holysheep.ai") || provider.baseUrl.includes("127.0.0.1"));
|
|
3288
|
+
}
|
|
3289
|
+
__name(isHolySheepProvider, "isHolySheepProvider");
|
|
3290
|
+
function writeManagedConfig(baseConfig, bridgeBaseUrl, apiKey, primaryModel, selectedModels, gatewayPort) {
|
|
3291
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
3292
|
+
fs.mkdirSync(OPENCLAW_DIR, { recursive: true });
|
|
3293
|
+
const plan = buildManagedPlan(bridgeBaseUrl, apiKey, primaryModel, selectedModels);
|
|
3294
|
+
const existingProviders = ((_a = baseConfig == null ? void 0 : baseConfig.models) == null ? void 0 : _a.providers) || {};
|
|
3295
|
+
const managedProviderIds = Object.entries(existingProviders).filter(([providerId, provider]) => providerId === OPENCLAW_PROVIDER_NAME || isHolySheepProvider(provider)).map(([providerId]) => providerId);
|
|
3296
|
+
const preservedProviders = Object.fromEntries(
|
|
3297
|
+
Object.entries(existingProviders).filter(([, provider]) => !isHolySheepProvider(provider))
|
|
3298
|
+
);
|
|
3299
|
+
const existingModelMap = ((_c = (_b = baseConfig == null ? void 0 : baseConfig.agents) == null ? void 0 : _b.defaults) == null ? void 0 : _c.models) || {};
|
|
3300
|
+
const preservedModelMap = Object.fromEntries(
|
|
3301
|
+
Object.entries(existingModelMap).filter(([ref]) => {
|
|
3302
|
+
return !managedProviderIds.some((providerId) => ref.startsWith(`${providerId}/`));
|
|
3303
|
+
})
|
|
3304
|
+
);
|
|
3305
|
+
const managedModelMap = Object.fromEntries(plan.managedModelRefs.map((ref) => [ref, {}]));
|
|
3306
|
+
const nextConfig = {
|
|
3307
|
+
...baseConfig,
|
|
3308
|
+
models: {
|
|
3309
|
+
...baseConfig.models || {},
|
|
3310
|
+
mode: "merge",
|
|
3311
|
+
providers: {
|
|
3312
|
+
...preservedProviders,
|
|
3313
|
+
...plan.providers
|
|
3314
|
+
}
|
|
3315
|
+
},
|
|
3316
|
+
agents: {
|
|
3317
|
+
...baseConfig.agents || {},
|
|
3318
|
+
defaults: {
|
|
3319
|
+
...((_d = baseConfig.agents) == null ? void 0 : _d.defaults) || {},
|
|
3320
|
+
model: {
|
|
3321
|
+
...((_f = (_e = baseConfig.agents) == null ? void 0 : _e.defaults) == null ? void 0 : _f.model) || {},
|
|
3322
|
+
primary: plan.primaryRef
|
|
3323
|
+
},
|
|
3324
|
+
models: {
|
|
3325
|
+
...preservedModelMap,
|
|
3326
|
+
...managedModelMap
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
},
|
|
3330
|
+
gateway: {
|
|
3331
|
+
...baseConfig.gateway || {},
|
|
3332
|
+
mode: "local",
|
|
3333
|
+
port: gatewayPort,
|
|
3334
|
+
bind: "loopback",
|
|
3335
|
+
auth: {
|
|
3336
|
+
...((_g = baseConfig.gateway) == null ? void 0 : _g.auth) || {},
|
|
3337
|
+
mode: "none"
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
};
|
|
3341
|
+
atomicWriteJson(CONFIG_FILE, nextConfig);
|
|
3342
|
+
return plan;
|
|
3343
|
+
}
|
|
3344
|
+
__name(writeManagedConfig, "writeManagedConfig");
|
|
3345
|
+
function _disableGatewayAuth(preferNpx = false) {
|
|
3346
|
+
try {
|
|
3347
|
+
runOpenClaw(["config", "set", "gateway.auth.mode", "none"], { preferNpx });
|
|
3348
|
+
} catch {
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
__name(_disableGatewayAuth, "_disableGatewayAuth");
|
|
3352
|
+
function _installGatewayService(port, preferNpx = false) {
|
|
3353
|
+
if (preferNpx) return false;
|
|
3354
|
+
const result = runOpenClaw(["gateway", "install", "--force", "--port", String(port)], {
|
|
3355
|
+
preferNpx,
|
|
3356
|
+
timeout: 6e4
|
|
3357
|
+
});
|
|
3358
|
+
return result.status === 0;
|
|
3359
|
+
}
|
|
3360
|
+
__name(_installGatewayService, "_installGatewayService");
|
|
3361
|
+
function _startGateway(port, preferNpx = false, preferService = true) {
|
|
3362
|
+
const serviceResult = preferService ? runOpenClaw(["gateway", "start"], { preferNpx, timeout: 2e4 }) : { status: 1 };
|
|
3363
|
+
let directChild = null;
|
|
3364
|
+
if (serviceResult.status !== 0) {
|
|
3365
|
+
directChild = spawnOpenClaw(["gateway", "--port", String(port)], {
|
|
3366
|
+
preferNpx,
|
|
3367
|
+
detached: true,
|
|
3368
|
+
stdio: "ignore"
|
|
3369
|
+
});
|
|
3370
|
+
directChild.unref();
|
|
3371
|
+
}
|
|
3372
|
+
for (let i = 0; i < 8; i++) {
|
|
3373
|
+
const t0 = Date.now();
|
|
3374
|
+
while (Date.now() - t0 < 1e3) {
|
|
3375
|
+
}
|
|
3376
|
+
try {
|
|
3377
|
+
execSync(
|
|
3378
|
+
isWin ? `powershell -NonInteractive -Command "try{(Invoke-WebRequest -Uri http://127.0.0.1:${port}/ -TimeoutSec 1 -UseBasicParsing).StatusCode}catch{exit 1}"` : `curl -sf http://127.0.0.1:${port}/ -o /dev/null --max-time 1`,
|
|
3379
|
+
{ stdio: "ignore", timeout: 3e3 }
|
|
3380
|
+
);
|
|
3381
|
+
return {
|
|
3382
|
+
ok: true,
|
|
3383
|
+
pid: (directChild == null ? void 0 : directChild.pid) || null,
|
|
3384
|
+
mode: directChild ? "direct-process" : "daemon"
|
|
3385
|
+
};
|
|
3386
|
+
} catch {
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
return {
|
|
3390
|
+
ok: false,
|
|
3391
|
+
pid: (directChild == null ? void 0 : directChild.pid) || null,
|
|
3392
|
+
mode: directChild ? "direct-process" : "daemon"
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
3395
|
+
__name(_startGateway, "_startGateway");
|
|
3396
|
+
function getDashboardUrl(port, preferNpx = false) {
|
|
3397
|
+
const result = runOpenClaw(["dashboard", "--no-open"], {
|
|
3398
|
+
preferNpx,
|
|
3399
|
+
timeout: preferNpx ? 6e4 : 2e4
|
|
3400
|
+
});
|
|
3401
|
+
if (result.status === 0) {
|
|
3402
|
+
const output = String(result.stdout || "");
|
|
3403
|
+
const match = output.match(/Dashboard URL:\s*(\S+)/) || output.match(/(https?:\/\/\S+)/);
|
|
3404
|
+
if (match) return match[1];
|
|
3405
|
+
}
|
|
3406
|
+
return getDashboardUrlForPort(port);
|
|
3407
|
+
}
|
|
3408
|
+
__name(getDashboardUrl, "getDashboardUrl");
|
|
3409
|
+
module2.exports = {
|
|
3410
|
+
name: "OpenClaw",
|
|
3411
|
+
id: "openclaw",
|
|
3412
|
+
checkInstalled() {
|
|
3413
|
+
return detectRuntime().available;
|
|
3414
|
+
},
|
|
3415
|
+
detectRuntime,
|
|
3416
|
+
getVersion() {
|
|
3417
|
+
return detectRuntime().version;
|
|
3418
|
+
},
|
|
3419
|
+
isConfigured() {
|
|
3420
|
+
var _a, _b, _c, _d;
|
|
3421
|
+
const cfg = readConfig();
|
|
3422
|
+
const hasProvider = (_d = (_c = (_b = (_a = cfg == null ? void 0 : cfg.models) == null ? void 0 : _a.providers) == null ? void 0 : _b[OPENCLAW_PROVIDER_NAME]) == null ? void 0 : _c.baseUrl) == null ? void 0 : _d.includes("127.0.0.1");
|
|
3423
|
+
const bridge = readBridgeConfig();
|
|
3424
|
+
return Boolean(hasProvider && (bridge == null ? void 0 : bridge.apiKey));
|
|
3425
|
+
},
|
|
3426
|
+
configure(apiKey, baseUrlAnthropic, baseUrlOpenAI, primaryModel, selectedModels) {
|
|
3427
|
+
const chalk = require("chalk");
|
|
3428
|
+
console.log(chalk.gray("\n \u2699\uFE0F \u6B63\u5728\u914D\u7F6E OpenClaw..."));
|
|
3429
|
+
try {
|
|
3430
|
+
const pruned = pruneClobberedBackups();
|
|
3431
|
+
if (pruned.removed > 0) {
|
|
3432
|
+
console.log(chalk.gray(` \u2192 \u5DF2\u6E05\u7406 ${pruned.removed} \u4E2A\u8FC7\u671F\u7684 OpenClaw \u914D\u7F6E\u5907\u4EFD\uFF08>7 \u5929\u7684 .clobbered.* \u6587\u4EF6\uFF09`));
|
|
3433
|
+
}
|
|
3434
|
+
} catch {
|
|
3435
|
+
}
|
|
3436
|
+
const runtime = detectRuntime();
|
|
3437
|
+
if (!runtime.available) {
|
|
3438
|
+
throw new Error("\u672A\u68C0\u6D4B\u5230 OpenClaw\uFF1B\u8BF7\u5148\u5168\u5C40\u5B89\u88C5\uFF0C\u6216\u786E\u4FDD npx \u53EF\u7528");
|
|
3439
|
+
}
|
|
3440
|
+
this._lastRuntimeCommand = runtime.command;
|
|
3441
|
+
this._lastRuntimeVia = runtime.via;
|
|
3442
|
+
console.log(chalk.gray(" \u2192 \u505C\u6B62\u65E7\u7684 Bridge \u548C Gateway..."));
|
|
3443
|
+
stopBridge();
|
|
3444
|
+
runOpenClaw(["gateway", "stop"], { preferNpx: runtime.via === "npx" });
|
|
3445
|
+
const t0 = Date.now();
|
|
3446
|
+
while (Date.now() - t0 < 1e3) {
|
|
3447
|
+
}
|
|
3448
|
+
if (runtime.via === "npx" && removeBrokenLaunchAgent()) {
|
|
3449
|
+
console.log(chalk.gray(" \u2192 \u5DF2\u6E05\u7406\u65E7\u7684 OpenClaw \u5B88\u62A4\u8FDB\u7A0B\u914D\u7F6E\uFF08\u5931\u6548\u7684 npx \u7F13\u5B58\u8DEF\u5F84\uFF09"));
|
|
3450
|
+
}
|
|
3451
|
+
const resolvedPrimaryModel = pickPrimaryModel(primaryModel, selectedModels);
|
|
3452
|
+
const gatewayPort = findAvailableGatewayPort(DEFAULT_GATEWAY_PORT);
|
|
3453
|
+
if (!gatewayPort) {
|
|
3454
|
+
throw new Error(`\u627E\u4E0D\u5230\u53EF\u7528\u7AEF\u53E3\uFF08\u5DF2\u68C0\u67E5 ${DEFAULT_GATEWAY_PORT}-${DEFAULT_GATEWAY_PORT + MAX_PORT_SCAN - 1}\uFF09`);
|
|
3455
|
+
}
|
|
3456
|
+
this._lastGatewayPort = gatewayPort;
|
|
3457
|
+
const bridgePort = findAvailableGatewayPort(DEFAULT_BRIDGE_PORT, [gatewayPort]);
|
|
3458
|
+
if (!bridgePort) {
|
|
3459
|
+
throw new Error(`\u627E\u4E0D\u5230\u53EF\u7528\u6865\u63A5\u7AEF\u53E3\uFF08\u5DF2\u68C0\u67E5 ${DEFAULT_BRIDGE_PORT}-${DEFAULT_BRIDGE_PORT + MAX_PORT_SCAN - 1}\uFF09`);
|
|
3460
|
+
}
|
|
3461
|
+
this._lastBridgePort = bridgePort;
|
|
3462
|
+
writeBridgeConfig({
|
|
3463
|
+
port: bridgePort,
|
|
3464
|
+
host: "127.0.0.1",
|
|
3465
|
+
apiKey,
|
|
3466
|
+
baseUrlAnthropic,
|
|
3467
|
+
baseUrlOpenAI,
|
|
3468
|
+
models: normalizeRequestedModels(selectedModels),
|
|
3469
|
+
gatewayPort,
|
|
3470
|
+
gatewayHost: "127.0.0.1",
|
|
3471
|
+
gatewayPid: null,
|
|
3472
|
+
gatewayLaunchMode: null,
|
|
3473
|
+
watchdog: {
|
|
3474
|
+
enabled: true,
|
|
3475
|
+
intervalMs: 3e3,
|
|
3476
|
+
failureThreshold: 3,
|
|
3477
|
+
startupGraceMs: 3e4,
|
|
3478
|
+
requestTimeoutMs: 1500
|
|
3479
|
+
}
|
|
3480
|
+
});
|
|
3481
|
+
console.log(chalk.gray(" \u2192 \u6B63\u5728\u542F\u52A8 HolySheep Bridge..."));
|
|
3482
|
+
if (!startBridge(bridgePort)) {
|
|
3483
|
+
throw new Error("HolySheep OpenClaw Bridge \u542F\u52A8\u5931\u8D25");
|
|
3484
|
+
}
|
|
3485
|
+
const bridgeBaseUrl = getBridgeBaseUrl(bridgePort);
|
|
3486
|
+
if (gatewayPort !== DEFAULT_GATEWAY_PORT) {
|
|
3487
|
+
console.log(chalk.yellow(` \u26A0\uFE0F \u7AEF\u53E3 ${DEFAULT_GATEWAY_PORT} \u5DF2\u5360\u7528\uFF0C\u81EA\u52A8\u5207\u6362\u5230 ${gatewayPort}`));
|
|
3488
|
+
const listeners = listPortListeners(DEFAULT_GATEWAY_PORT);
|
|
3489
|
+
if (listeners.length) {
|
|
3490
|
+
const summary = listeners.slice(0, 2).map((item) => `${item.command}(${item.pid})`).join(", ");
|
|
3491
|
+
console.log(chalk.gray(` \u5360\u7528\u8FDB\u7A0B: ${summary}`));
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
try {
|
|
3495
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
3496
|
+
} catch {
|
|
3497
|
+
}
|
|
3498
|
+
console.log(chalk.gray(" \u2192 \u5199\u5165\u914D\u7F6E..."));
|
|
3499
|
+
const onboardArgs = [
|
|
3500
|
+
"onboard",
|
|
3501
|
+
"--non-interactive",
|
|
3502
|
+
"--accept-risk",
|
|
3503
|
+
"--auth-choice",
|
|
3504
|
+
"custom-api-key",
|
|
3505
|
+
"--custom-base-url",
|
|
3506
|
+
bridgeBaseUrl,
|
|
3507
|
+
"--custom-api-key",
|
|
3508
|
+
apiKey,
|
|
3509
|
+
"--custom-model-id",
|
|
3510
|
+
resolvedPrimaryModel.replace(/\[.*\]/, ""),
|
|
3511
|
+
"--custom-compatibility",
|
|
3512
|
+
"openai",
|
|
3513
|
+
"--gateway-port",
|
|
3514
|
+
String(gatewayPort)
|
|
3515
|
+
];
|
|
3516
|
+
if (runtime.via !== "npx") onboardArgs.push("--install-daemon");
|
|
3517
|
+
const result = runOpenClaw(onboardArgs, { preferNpx: runtime.via === "npx" });
|
|
3518
|
+
if (result.status !== 0) {
|
|
3519
|
+
console.log(chalk.yellow(" \u26A0\uFE0F onboard \u5931\u8D25\uFF0C\u4F7F\u7528\u5907\u7528\u914D\u7F6E..."));
|
|
3520
|
+
}
|
|
3521
|
+
const plan = writeManagedConfig(
|
|
3522
|
+
result.status === 0 ? readConfig() : {},
|
|
3523
|
+
bridgeBaseUrl,
|
|
3524
|
+
apiKey,
|
|
3525
|
+
resolvedPrimaryModel,
|
|
3526
|
+
selectedModels,
|
|
3527
|
+
gatewayPort
|
|
3528
|
+
);
|
|
3529
|
+
_disableGatewayAuth(runtime.via === "npx");
|
|
3530
|
+
const serviceReady = _installGatewayService(gatewayPort, runtime.via === "npx");
|
|
3531
|
+
if (runtime.via === "npx") {
|
|
3532
|
+
console.log(chalk.gray(" \u2192 \u5F53\u524D\u4EC5\u68C0\u6D4B\u5230 npx openclaw\uFF0C\u8DF3\u8FC7 daemon \u5B89\u88C5\uFF0C\u6539\u4E3A\u76F4\u63A5\u542F\u52A8 Gateway \u8FDB\u7A0B"));
|
|
3533
|
+
}
|
|
3534
|
+
console.log(chalk.gray(" \u2192 \u6B63\u5728\u542F\u52A8 Gateway..."));
|
|
3535
|
+
const gatewayState = _startGateway(gatewayPort, runtime.via === "npx", serviceReady);
|
|
3536
|
+
updateBridgeConfig({
|
|
3537
|
+
gatewayPort,
|
|
3538
|
+
gatewayPid: gatewayState.pid,
|
|
3539
|
+
gatewayLaunchMode: gatewayState.mode,
|
|
3540
|
+
gatewayStartedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3541
|
+
});
|
|
3542
|
+
if (gatewayState.ok) {
|
|
3543
|
+
console.log(chalk.green(" \u2713 OpenClaw Gateway \u5DF2\u542F\u52A8"));
|
|
3544
|
+
} else {
|
|
3545
|
+
console.log(chalk.yellow(" \u26A0\uFE0F Gateway \u672A\u5C31\u7EEA\uFF1B\u5F53\u524D\u4E0D\u8981\u6253\u5F00 about:blank \u6216\u88F8\u6D4F\u89C8\u5668\u58F3\u7A97\u53E3"));
|
|
3546
|
+
}
|
|
3547
|
+
const dashUrl = gatewayState.ok ? getDashboardUrl(gatewayPort, runtime.via === "npx") : getDashboardUrlForPort(gatewayPort);
|
|
3548
|
+
console.log(chalk.cyan("\n \u2192 \u6D4F\u89C8\u5668\u6253\u5F00\uFF08\u63A8\u8350\u4F7F\u7528\u6B64\u5730\u5740\uFF09:"));
|
|
3549
|
+
console.log(chalk.bold.cyan(` ${dashUrl}`));
|
|
3550
|
+
console.log(chalk.gray(` Bridge \u5730\u5740: ${bridgeBaseUrl}`));
|
|
3551
|
+
console.log(chalk.gray(` \u9ED8\u8BA4\u6A21\u578B: ${plan.primaryRef || OPENCLAW_DEFAULT_MODEL}`));
|
|
3552
|
+
console.log(chalk.gray(` Gateway \u542F\u52A8\u65B9\u5F0F: ${gatewayState.mode}`));
|
|
3553
|
+
console.log(chalk.gray(" \u6D4F\u89C8\u5668\u5E94\u76F4\u63A5\u6253\u5F00 dashboard URL\uFF0C\u4E0D\u5E94\u505C\u5728 about:blank"));
|
|
3554
|
+
console.log(chalk.gray(" Bridge \u4F1A\u5728\u68C0\u6D4B\u5230 OpenClaw Gateway \u6301\u7EED\u4E0D\u53EF\u7528\u540E\u81EA\u52A8\u9000\u51FA"));
|
|
3555
|
+
console.log(chalk.gray(" \u5982\u5728 Windows \u4E0A\u6253\u5F00\u88F8 http://127.0.0.1:PORT/ \u4ECD\u62A5 Unauthorized\uFF0C\u8BF7\u4F7F\u7528\u4E0A\u9762\u7684 dashboard \u5730\u5740"));
|
|
3556
|
+
return {
|
|
3557
|
+
file: CONFIG_FILE,
|
|
3558
|
+
hot: false,
|
|
3559
|
+
dashboardUrl: dashUrl,
|
|
3560
|
+
gatewayPort,
|
|
3561
|
+
gatewayReady: gatewayState.ok,
|
|
3562
|
+
gatewayLaunchMode: gatewayState.mode,
|
|
3563
|
+
launchCmd: getLaunchCommand(gatewayPort)
|
|
3564
|
+
};
|
|
3565
|
+
},
|
|
3566
|
+
reset() {
|
|
3567
|
+
stopBridge();
|
|
3568
|
+
try {
|
|
3569
|
+
runOpenClaw(["gateway", "stop"], { preferNpx: detectRuntime().via === "npx" });
|
|
3570
|
+
} catch {
|
|
3571
|
+
}
|
|
3572
|
+
try {
|
|
3573
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
3574
|
+
} catch {
|
|
3575
|
+
}
|
|
3576
|
+
try {
|
|
3577
|
+
fs.unlinkSync(BRIDGE_CONFIG_FILE);
|
|
3578
|
+
} catch {
|
|
3579
|
+
}
|
|
3580
|
+
},
|
|
3581
|
+
getConfigPath() {
|
|
3582
|
+
return CONFIG_FILE;
|
|
3583
|
+
},
|
|
3584
|
+
getBridgePort() {
|
|
3585
|
+
return getConfiguredBridgePort();
|
|
3586
|
+
},
|
|
3587
|
+
getGatewayPort() {
|
|
3588
|
+
return getConfiguredGatewayPort();
|
|
3589
|
+
},
|
|
3590
|
+
ensureBridgeRunning(port) {
|
|
3591
|
+
port = port || getConfiguredBridgePort();
|
|
3592
|
+
return startBridge(port);
|
|
3593
|
+
},
|
|
3594
|
+
ensureGatewayRunning(port) {
|
|
3595
|
+
port = port || getConfiguredGatewayPort();
|
|
3596
|
+
try {
|
|
3597
|
+
const checkCmd = isWin ? `powershell -NonInteractive -Command "if(Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue){exit 0}else{exit 1}"` : `curl -so /dev/null --max-time 1 http://127.0.0.1:${port}/ || lsof -iTCP:${port} -sTCP:LISTEN -t >/dev/null 2>&1`;
|
|
3598
|
+
execSync(checkCmd, { stdio: "ignore", timeout: 5e3 });
|
|
3599
|
+
return true;
|
|
3600
|
+
} catch {
|
|
3601
|
+
}
|
|
3602
|
+
const preferNpx = !hasOpenClawBinary();
|
|
3603
|
+
const result = _startGateway(port, preferNpx, !preferNpx);
|
|
3604
|
+
return result.ok;
|
|
3605
|
+
},
|
|
3606
|
+
getPrimaryModel() {
|
|
3607
|
+
return getConfiguredPrimaryModel();
|
|
3608
|
+
},
|
|
3609
|
+
getPrimaryModelRoute() {
|
|
3610
|
+
return getPrimaryModelRoute();
|
|
3611
|
+
},
|
|
3612
|
+
getPortListeners(port = getConfiguredGatewayPort()) {
|
|
3613
|
+
return listPortListeners(port);
|
|
3614
|
+
},
|
|
3615
|
+
getLaunchAgentDiagnosis,
|
|
3616
|
+
get hint() {
|
|
3617
|
+
return `Bridge + Gateway \u5DF2\u914D\u7F6E\uFF0C\u9ED8\u8BA4\u6A21\u578B\u4E3A ${getConfiguredPrimaryModel() || OPENCLAW_DEFAULT_MODEL}`;
|
|
3618
|
+
},
|
|
3619
|
+
get launchSteps() {
|
|
3620
|
+
const bridgePort = getConfiguredBridgePort();
|
|
3621
|
+
const port = getConfiguredGatewayPort();
|
|
3622
|
+
return [
|
|
3623
|
+
{ cmd: getBridgeCommand(bridgePort), note: "\u5148\u542F\u52A8 HolySheep OpenClaw Bridge" },
|
|
3624
|
+
{ cmd: getLaunchCommand(port), note: "\u518D\u542F\u52A8 OpenClaw Gateway" },
|
|
3625
|
+
{ cmd: getDashboardCommand(), note: "\u518D\u751F\u6210/\u6253\u5F00\u53EF\u76F4\u63A5\u8FDE\u63A5\u7684 Dashboard \u5730\u5740\uFF08\u63A8\u8350\uFF09" }
|
|
3626
|
+
];
|
|
3627
|
+
},
|
|
3628
|
+
get launchNote() {
|
|
3629
|
+
const runtime = module2.exports._lastRuntimeVia === "npx" ? "\u5F53\u524D\u4E3A npx \u6A21\u5F0F\uFF0C\u4E0D\u5B89\u88C5\u5E38\u9A7B daemon\u3002" : "";
|
|
3630
|
+
return `\u{1F310} \u8BF7\u5148\u542F\u52A8 Bridge\uFF0C\u518D\u542F\u52A8 Gateway\uFF1B\u6700\u540E\u8FD0\u884C ${getDashboardCommand()} ${runtime}`.trim();
|
|
3631
|
+
},
|
|
3632
|
+
installCmd: "npm install -g openclaw@latest",
|
|
3633
|
+
docsUrl: "https://docs.openclaw.ai",
|
|
3634
|
+
// [HolySheep fork v2.1.38 / hs26] Test-only exports.
|
|
3635
|
+
_atomicWriteJson: atomicWriteJson,
|
|
3636
|
+
_pruneClobberedBackups: pruneClobberedBackups
|
|
3637
|
+
};
|
|
3638
|
+
}
|
|
3639
|
+
});
|
|
3640
|
+
|
|
3641
|
+
// src/tools/hermes.js
|
|
3642
|
+
var require_hermes = __commonJS({
|
|
3643
|
+
"src/tools/hermes.js"(exports2, module2) {
|
|
3644
|
+
var fs = require("fs");
|
|
3645
|
+
var path = require("path");
|
|
3646
|
+
var os = require("os");
|
|
3647
|
+
var { commandExists } = require_which();
|
|
3648
|
+
var CONFIG_DIR = path.join(os.homedir(), ".hermes");
|
|
3649
|
+
var CONFIG_FILE = path.join(CONFIG_DIR, "config.toml");
|
|
3650
|
+
var CONFIG_YAML = path.join(CONFIG_DIR, "config.yaml");
|
|
3651
|
+
var DEFAULT_PROVIDER_RE = /^default_provider\s*=\s*"[^"]*"\s*$/m;
|
|
3652
|
+
function readConfig() {
|
|
3653
|
+
try {
|
|
3654
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
3655
|
+
return fs.readFileSync(CONFIG_FILE, "utf8");
|
|
3656
|
+
}
|
|
3657
|
+
} catch {
|
|
3658
|
+
}
|
|
3659
|
+
return "";
|
|
3660
|
+
}
|
|
3661
|
+
__name(readConfig, "readConfig");
|
|
3662
|
+
function patchConfigYaml(apiKey, baseUrlOpenAI, primaryModel) {
|
|
3663
|
+
if (!fs.existsSync(CONFIG_YAML)) {
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3666
|
+
const cleanBase = String(baseUrlOpenAI || "https://api.holysheep.ai/v1").replace(/\/+$/, "");
|
|
3667
|
+
const model = primaryModel || "claude-sonnet-4-6";
|
|
3668
|
+
const src = fs.readFileSync(CONFIG_YAML, "utf8");
|
|
3669
|
+
const lines = src.replace(/\r\n/g, "\n").split("\n");
|
|
3670
|
+
const out = [];
|
|
3671
|
+
let inModel = false;
|
|
3672
|
+
let modelBlockLines = [];
|
|
3673
|
+
let modelIndent = " ";
|
|
3674
|
+
let modelBlockStart = -1;
|
|
3675
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3676
|
+
const line = lines[i];
|
|
3677
|
+
if (/^model:\s*$/.test(line)) {
|
|
3678
|
+
out.push(line);
|
|
3679
|
+
inModel = true;
|
|
3680
|
+
modelBlockStart = out.length;
|
|
3681
|
+
modelBlockLines = [];
|
|
3682
|
+
continue;
|
|
3683
|
+
}
|
|
3684
|
+
if (inModel) {
|
|
3685
|
+
if (line.length > 0 && !/^\s/.test(line)) {
|
|
3686
|
+
out.push(...rewriteModelBlock(modelBlockLines, cleanBase, apiKey, model, modelIndent));
|
|
3687
|
+
inModel = false;
|
|
3688
|
+
out.push(line);
|
|
3689
|
+
continue;
|
|
3690
|
+
}
|
|
3691
|
+
modelBlockLines.push(line);
|
|
3692
|
+
const m = line.match(/^(\s+)[^\s]/);
|
|
3693
|
+
if (m && modelBlockLines.length === 1) modelIndent = m[1];
|
|
3694
|
+
continue;
|
|
3695
|
+
}
|
|
3696
|
+
out.push(line);
|
|
3697
|
+
}
|
|
3698
|
+
if (inModel) {
|
|
3699
|
+
out.push(...rewriteModelBlock(modelBlockLines, cleanBase, apiKey, model, modelIndent));
|
|
3700
|
+
}
|
|
3701
|
+
const resultLines = out.join("\n").split("\n");
|
|
3702
|
+
const providersBlock = [
|
|
3703
|
+
"providers:",
|
|
3704
|
+
" custom:",
|
|
3705
|
+
` base_url: ${cleanBase}`,
|
|
3706
|
+
` api_key: ${apiKey}`,
|
|
3707
|
+
` default_model: ${model}`,
|
|
3708
|
+
" type: openai"
|
|
3709
|
+
];
|
|
3710
|
+
let start = -1, end = -1;
|
|
3711
|
+
for (let i = 0; i < resultLines.length; i++) {
|
|
3712
|
+
if (/^providers:\s*($|\{\s*\}\s*$)/.test(resultLines[i])) {
|
|
3713
|
+
start = i;
|
|
3714
|
+
break;
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
if (start >= 0) {
|
|
3718
|
+
end = resultLines.length;
|
|
3719
|
+
for (let j = start + 1; j < resultLines.length; j++) {
|
|
3720
|
+
const ln = resultLines[j];
|
|
3721
|
+
if (ln.length > 0 && !/^\s/.test(ln)) {
|
|
3722
|
+
end = j;
|
|
3723
|
+
break;
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
resultLines.splice(start, end - start, ...providersBlock);
|
|
3727
|
+
}
|
|
3728
|
+
const finalLines = resultLines.slice();
|
|
3729
|
+
let inCustom = false;
|
|
3730
|
+
let itemStart = -1;
|
|
3731
|
+
let itemLines = [];
|
|
3732
|
+
const flushItem = /* @__PURE__ */ __name(() => {
|
|
3733
|
+
if (itemStart < 0) return;
|
|
3734
|
+
const blob = itemLines.join("\n");
|
|
3735
|
+
const isHolySheep = /name:\s*holysheep/i.test(blob) || /base_url:\s*http:\/\/127\.0\.0\.1:18788/.test(blob);
|
|
3736
|
+
if (isHolySheep) {
|
|
3737
|
+
for (let i = 0; i < itemLines.length; i++) {
|
|
3738
|
+
if (/^\s+-?\s*base_url:/.test(itemLines[i])) {
|
|
3739
|
+
itemLines[i] = itemLines[i].replace(/base_url:.*$/, `base_url: ${cleanBase}`);
|
|
3740
|
+
} else if (/^\s+api_key:/.test(itemLines[i])) {
|
|
3741
|
+
itemLines[i] = itemLines[i].replace(/api_key:.*$/, `api_key: ${apiKey}`);
|
|
3742
|
+
}
|
|
3743
|
+
}
|
|
3744
|
+
finalLines.splice(itemStart, itemLines.length, ...itemLines);
|
|
3745
|
+
}
|
|
3746
|
+
itemStart = -1;
|
|
3747
|
+
itemLines = [];
|
|
3748
|
+
}, "flushItem");
|
|
3749
|
+
for (let i = 0; i < finalLines.length; i++) {
|
|
3750
|
+
const ln = finalLines[i];
|
|
3751
|
+
if (/^custom_providers:\s*$/.test(ln)) {
|
|
3752
|
+
inCustom = true;
|
|
3753
|
+
continue;
|
|
3754
|
+
}
|
|
3755
|
+
if (inCustom) {
|
|
3756
|
+
if (ln.length > 0 && !/^\s/.test(ln) && !/^-/.test(ln)) {
|
|
3757
|
+
flushItem();
|
|
3758
|
+
inCustom = false;
|
|
3759
|
+
continue;
|
|
3760
|
+
}
|
|
3761
|
+
if (/^\s*-\s/.test(ln)) {
|
|
3762
|
+
flushItem();
|
|
3763
|
+
itemStart = i;
|
|
3764
|
+
itemLines = [ln];
|
|
3765
|
+
continue;
|
|
3766
|
+
}
|
|
3767
|
+
if (itemStart >= 0) itemLines.push(ln);
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
if (inCustom) flushItem();
|
|
3771
|
+
fs.writeFileSync(CONFIG_YAML, finalLines.join("\n"), { encoding: "utf8", mode: 384 });
|
|
3772
|
+
}
|
|
3773
|
+
__name(patchConfigYaml, "patchConfigYaml");
|
|
3774
|
+
function rewriteModelBlock(blockLines, cleanBase, apiKey, model, indent) {
|
|
3775
|
+
let sawBase = false, sawKey = false, sawDefault = false;
|
|
3776
|
+
const rewritten = [];
|
|
3777
|
+
for (const line of blockLines) {
|
|
3778
|
+
if (/^\s*base_url\s*:/.test(line)) {
|
|
3779
|
+
rewritten.push(`${indent}base_url: ${cleanBase}`);
|
|
3780
|
+
sawBase = true;
|
|
3781
|
+
continue;
|
|
3782
|
+
}
|
|
3783
|
+
if (/^\s*api_key\s*:/.test(line)) {
|
|
3784
|
+
rewritten.push(`${indent}api_key: ${apiKey}`);
|
|
3785
|
+
sawKey = true;
|
|
3786
|
+
continue;
|
|
3787
|
+
}
|
|
3788
|
+
if (/^\s*default\s*:/.test(line)) {
|
|
3789
|
+
rewritten.push(`${indent}default: ${model}`);
|
|
3790
|
+
sawDefault = true;
|
|
3791
|
+
continue;
|
|
3792
|
+
}
|
|
3793
|
+
rewritten.push(line);
|
|
3794
|
+
}
|
|
3795
|
+
let insertAt = rewritten.length;
|
|
3796
|
+
while (insertAt > 0 && rewritten[insertAt - 1].trim() === "") insertAt--;
|
|
3797
|
+
const toInsert = [];
|
|
3798
|
+
if (!sawBase) toInsert.push(`${indent}base_url: ${cleanBase}`);
|
|
3799
|
+
if (!sawKey) toInsert.push(`${indent}api_key: ${apiKey}`);
|
|
3800
|
+
if (!sawDefault) toInsert.push(`${indent}default: ${model}`);
|
|
3801
|
+
if (toInsert.length) rewritten.splice(insertAt, 0, ...toInsert);
|
|
3802
|
+
return rewritten;
|
|
3803
|
+
}
|
|
3804
|
+
__name(rewriteModelBlock, "rewriteModelBlock");
|
|
3805
|
+
function writeConfig(content) {
|
|
3806
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
3807
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
3808
|
+
}
|
|
3809
|
+
fs.writeFileSync(CONFIG_FILE, content, { encoding: "utf8", mode: 384 });
|
|
3810
|
+
if (process.platform !== "win32") {
|
|
3811
|
+
try {
|
|
3812
|
+
fs.chmodSync(CONFIG_FILE, 384);
|
|
3813
|
+
} catch {
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
__name(writeConfig, "writeConfig");
|
|
3818
|
+
function buildHolySheepBlock(apiKey, baseUrlOpenAI, primaryModel) {
|
|
3819
|
+
const cleanBase = String(baseUrlOpenAI || "https://api.holysheep.ai/v1").replace(/\/+$/, "");
|
|
3820
|
+
const model = primaryModel || "gpt-5.4";
|
|
3821
|
+
return [
|
|
3822
|
+
"[providers.holysheep]",
|
|
3823
|
+
"# Managed by @simonyea/holysheep-cli. Do not edit by hand \u2014 run `hs setup`.",
|
|
3824
|
+
'type = "openai"',
|
|
3825
|
+
`base_url = "${cleanBase}"`,
|
|
3826
|
+
`api_key = "${apiKey}"`,
|
|
3827
|
+
`default_model = "${model}"`,
|
|
3828
|
+
""
|
|
3829
|
+
].join("\n");
|
|
3830
|
+
}
|
|
3831
|
+
__name(buildHolySheepBlock, "buildHolySheepBlock");
|
|
3832
|
+
function stripExisting(content) {
|
|
3833
|
+
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
|
3834
|
+
const out = [];
|
|
3835
|
+
let inBlock = false;
|
|
3836
|
+
for (const line of lines) {
|
|
3837
|
+
if (/^\[providers\.holysheep\]\s*$/.test(line)) {
|
|
3838
|
+
inBlock = true;
|
|
3839
|
+
continue;
|
|
3840
|
+
}
|
|
3841
|
+
if (inBlock) {
|
|
3842
|
+
if (/^\[[^\]]+\]\s*$/.test(line)) {
|
|
3843
|
+
inBlock = false;
|
|
3844
|
+
out.push(line);
|
|
3845
|
+
continue;
|
|
3846
|
+
}
|
|
3847
|
+
continue;
|
|
3848
|
+
}
|
|
3849
|
+
out.push(line);
|
|
3850
|
+
}
|
|
3851
|
+
return out.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
3852
|
+
}
|
|
3853
|
+
__name(stripExisting, "stripExisting");
|
|
3854
|
+
function mergeConfig(apiKey, baseUrlOpenAI, primaryModel) {
|
|
3855
|
+
const existing = stripExisting(readConfig());
|
|
3856
|
+
const block = buildHolySheepBlock(apiKey, baseUrlOpenAI, primaryModel);
|
|
3857
|
+
let withDefault = existing;
|
|
3858
|
+
if (DEFAULT_PROVIDER_RE.test(withDefault)) {
|
|
3859
|
+
withDefault = withDefault.replace(DEFAULT_PROVIDER_RE, 'default_provider = "holysheep"');
|
|
3860
|
+
} else {
|
|
3861
|
+
withDefault = `default_provider = "holysheep"
|
|
3862
|
+
` + withDefault;
|
|
3863
|
+
}
|
|
3864
|
+
const final = [withDefault.trim(), "", block].filter(Boolean).join("\n");
|
|
3865
|
+
return final.endsWith("\n") ? final : final + "\n";
|
|
3866
|
+
}
|
|
3867
|
+
__name(mergeConfig, "mergeConfig");
|
|
3868
|
+
function isConfiguredInToml(content) {
|
|
3869
|
+
if (!content) return false;
|
|
3870
|
+
return /\[providers\.holysheep\]/.test(content) && /api\.holysheep\.ai/.test(content) && /api_key\s*=\s*"cr_/.test(content);
|
|
3871
|
+
}
|
|
3872
|
+
__name(isConfiguredInToml, "isConfiguredInToml");
|
|
3873
|
+
function installCmdForPlatform() {
|
|
3874
|
+
if (process.platform === "win32") {
|
|
3875
|
+
return "";
|
|
3876
|
+
}
|
|
3877
|
+
return "curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash -s -- --skip-setup";
|
|
3878
|
+
}
|
|
3879
|
+
__name(installCmdForPlatform, "installCmdForPlatform");
|
|
3880
|
+
module2.exports = {
|
|
3881
|
+
name: "Hermes Agent",
|
|
3882
|
+
id: "hermes",
|
|
3883
|
+
checkInstalled() {
|
|
3884
|
+
return commandExists("hermes");
|
|
3885
|
+
},
|
|
3886
|
+
isConfigured() {
|
|
3887
|
+
return isConfiguredInToml(readConfig());
|
|
3888
|
+
},
|
|
3889
|
+
configure(apiKey, _baseUrlAnthropic, baseUrlOpenAI, primaryModel) {
|
|
3890
|
+
const hermesPrimaryModel = /^claude-/i.test(primaryModel || "") ? "gpt-5.4" : primaryModel || "gpt-5.4";
|
|
3891
|
+
const merged = mergeConfig(apiKey, baseUrlOpenAI, hermesPrimaryModel);
|
|
3892
|
+
writeConfig(merged);
|
|
3893
|
+
try {
|
|
3894
|
+
patchConfigYaml(apiKey, baseUrlOpenAI, hermesPrimaryModel);
|
|
3895
|
+
} catch (e) {
|
|
3896
|
+
try {
|
|
3897
|
+
process.stderr.write(`[hermes.configure] config.yaml patch skipped: ${e.message}
|
|
3898
|
+
`);
|
|
3899
|
+
} catch {
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
return {
|
|
3903
|
+
file: CONFIG_FILE,
|
|
3904
|
+
hot: true
|
|
3905
|
+
// next `hermes` run picks up the TOML on load
|
|
3906
|
+
};
|
|
3907
|
+
},
|
|
3908
|
+
reset() {
|
|
3909
|
+
const existing = readConfig();
|
|
3910
|
+
if (!existing) return;
|
|
3911
|
+
const stripped = stripExisting(existing).replace(DEFAULT_PROVIDER_RE, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
3912
|
+
writeConfig(stripped ? stripped + "\n" : "");
|
|
3913
|
+
},
|
|
3914
|
+
getConfigPath() {
|
|
3915
|
+
return CONFIG_FILE;
|
|
3916
|
+
},
|
|
3917
|
+
hint: process.platform === "win32" ? "Hermes \u5B98\u65B9\u5B89\u88C5\u811A\u672C\u4EC5\u652F\u6301 macOS/Linux/WSL2\uFF0CWindows \u8BF7\u5148\u542F\u7528 WSL2\u3002" : "\u5DF2\u5199\u5165 ~/.hermes/config.toml\uFF1B\u8FD0\u884C `hermes` \u9ED8\u8BA4\u4F7F\u7528 HolySheep\u3002",
|
|
3918
|
+
launchCmd: "hermes",
|
|
3919
|
+
installCmd: installCmdForPlatform(),
|
|
3920
|
+
docsUrl: "https://hermes-agent.nousresearch.com/docs/",
|
|
3921
|
+
// Internal helpers — re-exported for tests / inspection.
|
|
3922
|
+
_stripExisting: stripExisting,
|
|
3923
|
+
_mergeConfig: mergeConfig,
|
|
3924
|
+
_buildHolySheepBlock: buildHolySheepBlock
|
|
3925
|
+
};
|
|
3926
|
+
}
|
|
3927
|
+
});
|
|
3928
|
+
|
|
3929
|
+
// package.json
|
|
3930
|
+
var require_package = __commonJS({
|
|
3931
|
+
"package.json"(exports2, module2) {
|
|
3932
|
+
module2.exports = {
|
|
3933
|
+
name: "@simonyea/holysheep-cli",
|
|
3934
|
+
version: "2.1.42",
|
|
3935
|
+
description: "Claude Code/Cursor/Cline API relay for China \u2014 \xA51=$1, WeChat/Alipay payment, no credit card, no VPN. One command setup for all AI coding tools.",
|
|
3936
|
+
scripts: {
|
|
3937
|
+
build: "node scripts/build.mjs",
|
|
3938
|
+
test: "node tests/droid.test.js && node tests/workspace-store.test.js && node tests/runtime-stale-upgrade.test.js && node tests/hermes.test.js && node tests/preflight.test.js && node tests/opencode-auth-purge.test.js && node tests/shell-winpath.test.js && node tests/openclaw-atomic-write.test.js && node tests/opencode-default-model.test.js && node tests/paths-bundled.test.js",
|
|
3939
|
+
prepublishOnly: "npm run build && npm test && node scripts/check-tarball-size.js"
|
|
3940
|
+
},
|
|
3941
|
+
keywords: [
|
|
3942
|
+
"openai-china",
|
|
3943
|
+
"claude-china",
|
|
3944
|
+
"anthropic-china",
|
|
3945
|
+
"api-relay-china",
|
|
3946
|
+
"claude-proxy",
|
|
3947
|
+
"openai-proxy",
|
|
3948
|
+
"gemini-proxy",
|
|
3949
|
+
"china-ai-api",
|
|
3950
|
+
"claude",
|
|
3951
|
+
"codex",
|
|
3952
|
+
"gemini",
|
|
3953
|
+
"cursor",
|
|
3954
|
+
"aider",
|
|
3955
|
+
"opencode",
|
|
3956
|
+
"openclaw",
|
|
3957
|
+
"cline",
|
|
3958
|
+
"continue",
|
|
3959
|
+
"api",
|
|
3960
|
+
"relay",
|
|
3961
|
+
"proxy",
|
|
3962
|
+
"china",
|
|
3963
|
+
"holysheep",
|
|
3964
|
+
"ai",
|
|
3965
|
+
"coding",
|
|
3966
|
+
"openai",
|
|
3967
|
+
"anthropic",
|
|
3968
|
+
"gpt",
|
|
3969
|
+
"llm",
|
|
3970
|
+
"api-key",
|
|
3971
|
+
"base-url",
|
|
3972
|
+
"claude-code-china",
|
|
3973
|
+
"claude-code-relay",
|
|
3974
|
+
"wechat-pay-api",
|
|
3975
|
+
"alipay-api",
|
|
3976
|
+
"no-credit-card"
|
|
3977
|
+
],
|
|
3978
|
+
homepage: "https://holysheep.ai",
|
|
3979
|
+
repository: {
|
|
3980
|
+
type: "git",
|
|
3981
|
+
url: "git+https://gitee.com/simonyea/holysheep-cli.git"
|
|
3982
|
+
},
|
|
3983
|
+
license: "MIT",
|
|
3984
|
+
bin: {
|
|
3985
|
+
hs: "dist/index.js",
|
|
3986
|
+
holysheep: "dist/index.js"
|
|
3987
|
+
},
|
|
3988
|
+
type: "commonjs",
|
|
3989
|
+
main: "dist/index.js",
|
|
3990
|
+
files: [
|
|
3991
|
+
"dist/index.js",
|
|
3992
|
+
"dist/configure-worker.js",
|
|
3993
|
+
"dist/process-proxy-inject.js",
|
|
3994
|
+
"dist/index.html",
|
|
3995
|
+
"dist/pty-hermes-wrapper.py",
|
|
3996
|
+
"README.md",
|
|
3997
|
+
"LICENSE"
|
|
3998
|
+
],
|
|
3999
|
+
engines: {
|
|
4000
|
+
node: ">=16.0.0"
|
|
4001
|
+
},
|
|
4002
|
+
dependencies: {
|
|
4003
|
+
chalk: "^4.1.2",
|
|
4004
|
+
commander: "^11.1.0",
|
|
4005
|
+
inquirer: "^8.2.6",
|
|
4006
|
+
"node-fetch": "^2.7.0",
|
|
4007
|
+
ora: "^5.4.1"
|
|
4008
|
+
},
|
|
4009
|
+
devDependencies: {
|
|
4010
|
+
esbuild: "^0.28.0"
|
|
4011
|
+
}
|
|
4012
|
+
};
|
|
4013
|
+
}
|
|
4014
|
+
});
|
|
4015
|
+
|
|
4016
|
+
// src/utils/shell.js
|
|
4017
|
+
var require_shell = __commonJS({
|
|
4018
|
+
"src/utils/shell.js"(exports2, module2) {
|
|
4019
|
+
var fs = require("fs");
|
|
4020
|
+
var path = require("path");
|
|
4021
|
+
var os = require("os");
|
|
4022
|
+
var { execSync } = require("child_process");
|
|
4023
|
+
var pkg = require_package();
|
|
4024
|
+
var MARKER_START = "# >>> holysheep-cli managed >>>";
|
|
4025
|
+
var MARKER_END = "# <<< holysheep-cli managed <<<";
|
|
4026
|
+
function getShellRcFiles() {
|
|
4027
|
+
const home = os.homedir();
|
|
4028
|
+
if (process.platform === "win32") return [];
|
|
4029
|
+
const shell = process.env.SHELL || "";
|
|
4030
|
+
const candidates = [];
|
|
4031
|
+
if (shell.includes("zsh")) candidates.push(path.join(home, ".zshrc"));
|
|
4032
|
+
if (shell.includes("bash")) candidates.push(path.join(home, ".bashrc"), path.join(home, ".bash_profile"));
|
|
4033
|
+
if (shell.includes("fish")) candidates.push(path.join(home, ".config", "fish", "config.fish"));
|
|
4034
|
+
if (candidates.length === 0) {
|
|
4035
|
+
const zshrc = path.join(home, ".zshrc");
|
|
4036
|
+
const bashrc = path.join(home, ".bashrc");
|
|
4037
|
+
if (fs.existsSync(zshrc)) candidates.push(zshrc);
|
|
4038
|
+
if (fs.existsSync(bashrc)) candidates.push(bashrc);
|
|
4039
|
+
if (candidates.length === 0) candidates.push(zshrc);
|
|
4040
|
+
}
|
|
4041
|
+
return candidates;
|
|
4042
|
+
}
|
|
4043
|
+
__name(getShellRcFiles, "getShellRcFiles");
|
|
4044
|
+
function removeHsBlock(content) {
|
|
4045
|
+
const re = new RegExp(
|
|
4046
|
+
`\\n?${escapeRegex(MARKER_START)}[\\s\\S]*?${escapeRegex(MARKER_END)}\\n?`,
|
|
4047
|
+
"g"
|
|
4048
|
+
);
|
|
4049
|
+
return content.replace(re, "");
|
|
4050
|
+
}
|
|
4051
|
+
__name(removeHsBlock, "removeHsBlock");
|
|
4052
|
+
function removeStaleExports(content, keys, isFish = false) {
|
|
4053
|
+
let result = content;
|
|
4054
|
+
for (const key of keys) {
|
|
4055
|
+
if (isFish) {
|
|
4056
|
+
result = result.replace(new RegExp(`\\n?set\\s+-gx\\s+${escapeRegex(key)}\\s+[^\\n]*\\n?`, "g"), "\n");
|
|
4057
|
+
} else {
|
|
4058
|
+
result = result.replace(new RegExp(`\\n?export\\s+${escapeRegex(key)}=[^\\n]*\\n?`, "g"), "\n");
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
return result.replace(/\n{3,}/g, "\n\n");
|
|
4062
|
+
}
|
|
4063
|
+
__name(removeStaleExports, "removeStaleExports");
|
|
4064
|
+
function buildEnvBlock(envVars, isFish = false) {
|
|
4065
|
+
const lines = [MARKER_START];
|
|
4066
|
+
for (const [k, v] of Object.entries(envVars)) {
|
|
4067
|
+
lines.push(isFish ? `set -gx ${k} "${v}"` : `export ${k}="${v}"`);
|
|
4068
|
+
}
|
|
4069
|
+
lines.push(MARKER_END);
|
|
4070
|
+
return "\n" + lines.join("\n") + "\n";
|
|
4071
|
+
}
|
|
4072
|
+
__name(buildEnvBlock, "buildEnvBlock");
|
|
4073
|
+
function ensureWindowsUserPathHasNpmBin() {
|
|
4074
|
+
if (process.platform !== "win32") return [];
|
|
4075
|
+
const appData = process.env.APPDATA;
|
|
4076
|
+
if (!appData) return [];
|
|
4077
|
+
const npmBin = path.join(appData, "npm");
|
|
4078
|
+
let currentPath = "";
|
|
4079
|
+
try {
|
|
4080
|
+
currentPath = execSync(
|
|
4081
|
+
`powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "[Environment]::GetEnvironmentVariable('Path', 'User')"`,
|
|
4082
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
4083
|
+
).trim();
|
|
4084
|
+
} catch {
|
|
4085
|
+
currentPath = process.env.PATH || "";
|
|
4086
|
+
}
|
|
4087
|
+
const parts = currentPath.split(";").map((item) => item.trim()).filter(Boolean);
|
|
4088
|
+
const hasNpmBin = parts.some((item) => item.toLowerCase() === npmBin.toLowerCase());
|
|
4089
|
+
if (hasNpmBin) return [];
|
|
4090
|
+
const nextPath = [...parts, npmBin].join(";");
|
|
4091
|
+
try {
|
|
4092
|
+
const escapedPath = nextPath.replace(/'/g, "''");
|
|
4093
|
+
execSync(
|
|
4094
|
+
`powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "[Environment]::SetEnvironmentVariable('Path', '${escapedPath}', 'User')"`,
|
|
4095
|
+
{ stdio: "ignore" }
|
|
4096
|
+
);
|
|
4097
|
+
return ["[\u7528\u6237 PATH] %APPDATA%\\npm"];
|
|
4098
|
+
} catch {
|
|
4099
|
+
try {
|
|
4100
|
+
const chalk = require("chalk");
|
|
4101
|
+
console.warn(chalk.yellow(
|
|
4102
|
+
` \u26A0\uFE0F \u65E0\u6CD5\u81EA\u52A8\u66F4\u65B0 PATH\uFF0C\u8BF7\u624B\u52A8\u5C06\u4EE5\u4E0B\u8DEF\u5F84\u52A0\u5165\u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF PATH\uFF1A
|
|
4103
|
+
${npmBin}`
|
|
4104
|
+
));
|
|
4105
|
+
} catch {
|
|
4106
|
+
}
|
|
4107
|
+
return [];
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
__name(ensureWindowsUserPathHasNpmBin, "ensureWindowsUserPathHasNpmBin");
|
|
4111
|
+
function ensureWindowsUserPathHasLocalBin() {
|
|
4112
|
+
if (process.platform !== "win32") return [];
|
|
4113
|
+
const userProfile = process.env.USERPROFILE || os.homedir();
|
|
4114
|
+
if (!userProfile) return [];
|
|
4115
|
+
const localBin = path.join(userProfile, ".local", "bin");
|
|
4116
|
+
let currentPath = "";
|
|
4117
|
+
try {
|
|
4118
|
+
currentPath = execSync(
|
|
4119
|
+
`powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "[Environment]::GetEnvironmentVariable('Path', 'User')"`,
|
|
4120
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
4121
|
+
).trim();
|
|
4122
|
+
} catch {
|
|
4123
|
+
currentPath = process.env.PATH || "";
|
|
4124
|
+
}
|
|
4125
|
+
const parts = currentPath.split(";").map((item) => item.trim()).filter(Boolean);
|
|
4126
|
+
const hasLocalBin = parts.some((item) => item.toLowerCase() === localBin.toLowerCase());
|
|
4127
|
+
const procParts = (process.env.PATH || "").split(";").map((p) => p.trim()).filter(Boolean);
|
|
4128
|
+
const inProcessHasIt = procParts.some((item) => item.toLowerCase() === localBin.toLowerCase());
|
|
4129
|
+
if (!inProcessHasIt) {
|
|
4130
|
+
process.env.PATH = [...procParts, localBin].join(";");
|
|
4131
|
+
}
|
|
4132
|
+
if (hasLocalBin) return inProcessHasIt ? [] : ["[\u5F53\u524D\u8FDB\u7A0B PATH] %USERPROFILE%\\.local\\bin"];
|
|
4133
|
+
const nextPath = [...parts, localBin].join(";");
|
|
4134
|
+
try {
|
|
4135
|
+
const escapedPath = nextPath.replace(/'/g, "''");
|
|
4136
|
+
execSync(
|
|
4137
|
+
`powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "[Environment]::SetEnvironmentVariable('Path', '${escapedPath}', 'User')"`,
|
|
4138
|
+
{ stdio: "ignore" }
|
|
4139
|
+
);
|
|
4140
|
+
return ["[\u7528\u6237 PATH] %USERPROFILE%\\.local\\bin"];
|
|
4141
|
+
} catch {
|
|
4142
|
+
try {
|
|
4143
|
+
const chalk = require("chalk");
|
|
4144
|
+
console.warn(chalk.yellow(
|
|
4145
|
+
` \u26A0\uFE0F \u65E0\u6CD5\u81EA\u52A8\u66F4\u65B0 PATH\uFF0C\u8BF7\u624B\u52A8\u5C06\u4EE5\u4E0B\u8DEF\u5F84\u52A0\u5165\u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF PATH\uFF1A
|
|
4146
|
+
${localBin}`
|
|
4147
|
+
));
|
|
4148
|
+
} catch {
|
|
4149
|
+
}
|
|
4150
|
+
return [];
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
__name(ensureWindowsUserPathHasLocalBin, "ensureWindowsUserPathHasLocalBin");
|
|
4154
|
+
function installWindowsCliShims() {
|
|
4155
|
+
if (process.platform !== "win32") return [];
|
|
4156
|
+
const appData = process.env.APPDATA;
|
|
4157
|
+
if (!appData) return [];
|
|
4158
|
+
const npmBin = path.join(appData, "npm");
|
|
4159
|
+
fs.mkdirSync(npmBin, { recursive: true });
|
|
4160
|
+
if (fs.existsSync(path.join(npmBin, "hs.cmd"))) return [];
|
|
4161
|
+
const cliSpec = `@simonyea/holysheep-cli@${pkg.version}`;
|
|
4162
|
+
const cmdContent = [
|
|
4163
|
+
"@echo off",
|
|
4164
|
+
"setlocal",
|
|
4165
|
+
'if exist "%APPDATA%\\npm\\node_modules\\@simonyea\\holysheep-cli\\bin\\hs.js" (',
|
|
4166
|
+
' node "%APPDATA%\\npm\\node_modules\\@simonyea\\holysheep-cli\\bin\\hs.js" %*',
|
|
4167
|
+
') else if exist "%~dp0npx.cmd" (',
|
|
4168
|
+
` call "%~dp0npx.cmd" ${cliSpec} %*`,
|
|
4169
|
+
") else (",
|
|
4170
|
+
` call npx ${cliSpec} %*`,
|
|
4171
|
+
")",
|
|
4172
|
+
""
|
|
4173
|
+
].join("\r\n");
|
|
4174
|
+
const ps1Content = [
|
|
4175
|
+
'$localPkg = "$env:APPDATA\\npm\\node_modules\\@simonyea\\holysheep-cli\\bin\\hs.js"',
|
|
4176
|
+
"if (Test-Path $localPkg) {",
|
|
4177
|
+
" & node $localPkg @args",
|
|
4178
|
+
'} elseif (Test-Path (Join-Path $PSScriptRoot "npx.cmd")) {',
|
|
4179
|
+
` & (Join-Path $PSScriptRoot "npx.cmd") "${cliSpec}" @args`,
|
|
4180
|
+
"} else {",
|
|
4181
|
+
` & npx "${cliSpec}" @args`,
|
|
4182
|
+
"}",
|
|
4183
|
+
""
|
|
4184
|
+
].join("\r\n");
|
|
4185
|
+
const written = [];
|
|
4186
|
+
for (const name of ["hs", "holysheep"]) {
|
|
4187
|
+
fs.writeFileSync(path.join(npmBin, `${name}.cmd`), cmdContent, "utf8");
|
|
4188
|
+
fs.writeFileSync(path.join(npmBin, `${name}.ps1`), ps1Content, "utf8");
|
|
4189
|
+
written.push(`[\u542F\u52A8\u5668] %APPDATA%\\npm\\${name}.cmd`);
|
|
4190
|
+
}
|
|
4191
|
+
return written;
|
|
4192
|
+
}
|
|
4193
|
+
__name(installWindowsCliShims, "installWindowsCliShims");
|
|
4194
|
+
function writeEnvToShell(envVars) {
|
|
4195
|
+
if (process.platform === "win32") {
|
|
4196
|
+
const written2 = [];
|
|
4197
|
+
for (const [k, v] of Object.entries(envVars)) {
|
|
4198
|
+
try {
|
|
4199
|
+
execSync(`setx ${k} "${v}"`, { stdio: "ignore" });
|
|
4200
|
+
written2.push(`[\u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF] ${k}`);
|
|
4201
|
+
} catch {
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
written2.push(...installWindowsCliShims());
|
|
4205
|
+
written2.push(...ensureWindowsUserPathHasNpmBin());
|
|
4206
|
+
if (written2.length > 0) {
|
|
4207
|
+
const chalk = require("chalk");
|
|
4208
|
+
console.log(chalk.yellow("\n \u26A0\uFE0F Windows \u73AF\u5883\u53D8\u91CF\u5DF2\u5199\u5165\uFF0C\u9700\u8981\u91CD\u542F\u7EC8\u7AEF\u540E\u751F\u6548"));
|
|
4209
|
+
}
|
|
4210
|
+
return written2;
|
|
4211
|
+
}
|
|
4212
|
+
const files = getShellRcFiles();
|
|
4213
|
+
const written = [];
|
|
4214
|
+
for (const file of files) {
|
|
4215
|
+
let content = "";
|
|
4216
|
+
try {
|
|
4217
|
+
content = fs.readFileSync(file, "utf8");
|
|
4218
|
+
} catch {
|
|
4219
|
+
}
|
|
4220
|
+
const isFish = file.endsWith("config.fish");
|
|
4221
|
+
content = removeHsBlock(content);
|
|
4222
|
+
content = removeStaleExports(content, Object.keys(envVars), isFish);
|
|
4223
|
+
content += buildEnvBlock(envVars, isFish);
|
|
4224
|
+
fs.writeFileSync(file, content, "utf8");
|
|
4225
|
+
written.push(file);
|
|
4226
|
+
}
|
|
4227
|
+
return written;
|
|
4228
|
+
}
|
|
4229
|
+
__name(writeEnvToShell, "writeEnvToShell");
|
|
4230
|
+
function removeWindowsUserEnvVars(keys = []) {
|
|
4231
|
+
if (process.platform !== "win32") return [];
|
|
4232
|
+
const removed = [];
|
|
4233
|
+
for (const key of keys) {
|
|
4234
|
+
try {
|
|
4235
|
+
execSync(
|
|
4236
|
+
`powershell.exe -NoProfile -Command "[Environment]::SetEnvironmentVariable('${key}', $null, 'User')"`,
|
|
4237
|
+
{ stdio: "ignore" }
|
|
4238
|
+
);
|
|
4239
|
+
delete process.env[key];
|
|
4240
|
+
removed.push(`[\u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF] ${key}`);
|
|
4241
|
+
} catch {
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
return removed;
|
|
4245
|
+
}
|
|
4246
|
+
__name(removeWindowsUserEnvVars, "removeWindowsUserEnvVars");
|
|
4247
|
+
function removeEnvFromShell(extraKeys = []) {
|
|
4248
|
+
const HS_KEYS = [
|
|
4249
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
4250
|
+
"ANTHROPIC_API_KEY",
|
|
4251
|
+
"ANTHROPIC_BASE_URL",
|
|
4252
|
+
"OPENAI_API_KEY",
|
|
4253
|
+
"OPENAI_BASE_URL",
|
|
4254
|
+
"HOLYSHEEP_API_KEY",
|
|
4255
|
+
...extraKeys
|
|
4256
|
+
];
|
|
4257
|
+
if (process.platform === "win32") {
|
|
4258
|
+
return removeWindowsUserEnvVars(HS_KEYS);
|
|
4259
|
+
}
|
|
4260
|
+
const files = getShellRcFiles();
|
|
4261
|
+
const cleaned = [];
|
|
4262
|
+
for (const file of files) {
|
|
4263
|
+
if (!fs.existsSync(file)) continue;
|
|
4264
|
+
const isFish = file.endsWith("config.fish");
|
|
4265
|
+
let content = fs.readFileSync(file, "utf8");
|
|
4266
|
+
let updated = removeHsBlock(content);
|
|
4267
|
+
updated = removeStaleExports(updated, HS_KEYS, isFish);
|
|
4268
|
+
if (updated !== content) {
|
|
4269
|
+
fs.writeFileSync(file, updated, "utf8");
|
|
4270
|
+
cleaned.push(file);
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
return cleaned;
|
|
4274
|
+
}
|
|
4275
|
+
__name(removeEnvFromShell, "removeEnvFromShell");
|
|
4276
|
+
function escapeRegex(s) {
|
|
4277
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4278
|
+
}
|
|
4279
|
+
__name(escapeRegex, "escapeRegex");
|
|
4280
|
+
module2.exports = {
|
|
4281
|
+
getShellRcFiles,
|
|
4282
|
+
writeEnvToShell,
|
|
4283
|
+
removeEnvFromShell,
|
|
4284
|
+
ensureWindowsUserPathHasNpmBin,
|
|
4285
|
+
ensureWindowsUserPathHasLocalBin,
|
|
4286
|
+
installWindowsCliShims,
|
|
4287
|
+
removeWindowsUserEnvVars
|
|
4288
|
+
};
|
|
4289
|
+
}
|
|
4290
|
+
});
|
|
4291
|
+
|
|
4292
|
+
// src/tools/env-config.js
|
|
4293
|
+
var require_env_config = __commonJS({
|
|
4294
|
+
"src/tools/env-config.js"(exports2, module2) {
|
|
4295
|
+
"use strict";
|
|
4296
|
+
var { writeEnvToShell, removeEnvFromShell, getShellRcFiles } = require_shell();
|
|
4297
|
+
var { execSync } = require("child_process");
|
|
4298
|
+
var fs = require("fs");
|
|
4299
|
+
var path = require("path");
|
|
4300
|
+
var os = require("os");
|
|
4301
|
+
var MANAGED_KEYS = [
|
|
4302
|
+
"ANTHROPIC_API_KEY",
|
|
4303
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
4304
|
+
"ANTHROPIC_BASE_URL",
|
|
4305
|
+
"OPENAI_API_KEY",
|
|
4306
|
+
"OPENAI_BASE_URL"
|
|
4307
|
+
];
|
|
4308
|
+
var MARKER = "# >>> holysheep-cli managed >>>";
|
|
4309
|
+
var STATE_FILE = path.join(os.homedir(), ".holysheep", "env-config.json");
|
|
4310
|
+
function winSetEnvVar(key, value) {
|
|
4311
|
+
try {
|
|
4312
|
+
execSync(
|
|
4313
|
+
`powershell.exe -NoProfile -Command "[Environment]::SetEnvironmentVariable('${key}', '${value}', 'User')"`,
|
|
4314
|
+
{ stdio: "ignore", timeout: 1e4, windowsHide: true }
|
|
4315
|
+
);
|
|
4316
|
+
return true;
|
|
4317
|
+
} catch {
|
|
4318
|
+
return false;
|
|
4319
|
+
}
|
|
4320
|
+
}
|
|
4321
|
+
__name(winSetEnvVar, "winSetEnvVar");
|
|
4322
|
+
function winRemoveEnvVar(key) {
|
|
4323
|
+
try {
|
|
4324
|
+
execSync(
|
|
4325
|
+
`powershell.exe -NoProfile -Command "[Environment]::SetEnvironmentVariable('${key}', $null, 'User')"`,
|
|
4326
|
+
{ stdio: "ignore", timeout: 1e4, windowsHide: true }
|
|
4327
|
+
);
|
|
4328
|
+
} catch {
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
4331
|
+
__name(winRemoveEnvVar, "winRemoveEnvVar");
|
|
4332
|
+
function winGetEnvVar(key) {
|
|
4333
|
+
try {
|
|
4334
|
+
const out = execSync(
|
|
4335
|
+
`powershell.exe -NoProfile -Command "[Environment]::GetEnvironmentVariable('${key}', 'User')"`,
|
|
4336
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5e3, windowsHide: true }
|
|
4337
|
+
);
|
|
4338
|
+
const val = (out || "").trim();
|
|
4339
|
+
return val || null;
|
|
4340
|
+
} catch {
|
|
4341
|
+
return null;
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
__name(winGetEnvVar, "winGetEnvVar");
|
|
4345
|
+
function isConfiguredInShell() {
|
|
4346
|
+
if (process.platform === "win32") {
|
|
4347
|
+
const val = winGetEnvVar("ANTHROPIC_BASE_URL");
|
|
4348
|
+
return !!(val && val.includes("holysheep"));
|
|
4349
|
+
}
|
|
4350
|
+
try {
|
|
4351
|
+
const files = getShellRcFiles();
|
|
4352
|
+
for (const file of files) {
|
|
4353
|
+
try {
|
|
4354
|
+
const content = fs.readFileSync(file, "utf8");
|
|
4355
|
+
if (content.includes(MARKER) && content.includes("holysheep")) {
|
|
4356
|
+
return true;
|
|
4357
|
+
}
|
|
4358
|
+
} catch {
|
|
4359
|
+
}
|
|
4360
|
+
}
|
|
4361
|
+
} catch {
|
|
4362
|
+
}
|
|
4363
|
+
return false;
|
|
4364
|
+
}
|
|
4365
|
+
__name(isConfiguredInShell, "isConfiguredInShell");
|
|
4366
|
+
function saveState(envVars) {
|
|
4367
|
+
try {
|
|
4368
|
+
const dir = path.dirname(STATE_FILE);
|
|
4369
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
4370
|
+
fs.writeFileSync(STATE_FILE, JSON.stringify({ configuredAt: (/* @__PURE__ */ new Date()).toISOString(), keys: Object.keys(envVars) }, null, 2), "utf8");
|
|
4371
|
+
} catch {
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
__name(saveState, "saveState");
|
|
4375
|
+
function clearState() {
|
|
4376
|
+
try {
|
|
4377
|
+
fs.unlinkSync(STATE_FILE);
|
|
4378
|
+
} catch {
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
__name(clearState, "clearState");
|
|
4382
|
+
module2.exports = {
|
|
4383
|
+
name: "\u5168\u5C40\u73AF\u5883\u53D8\u91CF",
|
|
4384
|
+
id: "env-config",
|
|
4385
|
+
checkInstalled() {
|
|
4386
|
+
return true;
|
|
4387
|
+
},
|
|
4388
|
+
isConfigured() {
|
|
4389
|
+
return isConfiguredInShell();
|
|
4390
|
+
},
|
|
4391
|
+
configure(apiKey, baseUrlAnthropic, baseUrlOpenAI) {
|
|
4392
|
+
const envVars = {
|
|
4393
|
+
ANTHROPIC_API_KEY: apiKey,
|
|
4394
|
+
ANTHROPIC_AUTH_TOKEN: apiKey,
|
|
4395
|
+
ANTHROPIC_BASE_URL: baseUrlAnthropic,
|
|
4396
|
+
OPENAI_API_KEY: apiKey,
|
|
4397
|
+
OPENAI_BASE_URL: baseUrlOpenAI
|
|
4398
|
+
};
|
|
4399
|
+
let written;
|
|
4400
|
+
if (process.platform === "win32") {
|
|
4401
|
+
written = [];
|
|
4402
|
+
for (const [k, v] of Object.entries(envVars)) {
|
|
4403
|
+
if (winSetEnvVar(k, v)) {
|
|
4404
|
+
written.push(k);
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
} else {
|
|
4408
|
+
written = writeEnvToShell(envVars);
|
|
4409
|
+
}
|
|
4410
|
+
saveState(envVars);
|
|
4411
|
+
return {
|
|
4412
|
+
file: written.join(", ") || "shell env",
|
|
4413
|
+
hot: false
|
|
4414
|
+
};
|
|
4415
|
+
},
|
|
4416
|
+
reset() {
|
|
4417
|
+
if (process.platform === "win32") {
|
|
4418
|
+
for (const k of MANAGED_KEYS) winRemoveEnvVar(k);
|
|
4419
|
+
} else {
|
|
4420
|
+
removeEnvFromShell(MANAGED_KEYS);
|
|
4421
|
+
}
|
|
4422
|
+
clearState();
|
|
4423
|
+
},
|
|
4424
|
+
getConfigPath() {
|
|
4425
|
+
return null;
|
|
4426
|
+
},
|
|
4427
|
+
/**
|
|
4428
|
+
* 获取已配置的环境变量值(Windows 从注册表读,其他从 process.env 读)
|
|
4429
|
+
*/
|
|
4430
|
+
getConfiguredValues() {
|
|
4431
|
+
const result = {};
|
|
4432
|
+
for (const key of MANAGED_KEYS) {
|
|
4433
|
+
if (process.platform === "win32") {
|
|
4434
|
+
result[key] = winGetEnvVar(key) || null;
|
|
4435
|
+
} else {
|
|
4436
|
+
result[key] = process.env[key] || null;
|
|
4437
|
+
}
|
|
4438
|
+
}
|
|
4439
|
+
return result;
|
|
4440
|
+
},
|
|
4441
|
+
hint: "\u4E00\u952E\u914D\u7F6E\u5168\u5C40\u7EC8\u7AEF\u73AF\u5883\u53D8\u91CF\uFF08ANTHROPIC / OPENAI API Key + Base URL\uFF09",
|
|
4442
|
+
launchCmd: null,
|
|
4443
|
+
docsUrl: "https://holysheep.ai"
|
|
4444
|
+
};
|
|
4445
|
+
}
|
|
4446
|
+
});
|
|
4447
|
+
|
|
4448
|
+
// src/tools/index.js
|
|
4449
|
+
var require_tools = __commonJS({
|
|
4450
|
+
"src/tools/index.js"(exports2, module2) {
|
|
4451
|
+
module2.exports = [
|
|
4452
|
+
require_claude_code(),
|
|
4453
|
+
require_codex(),
|
|
4454
|
+
require_droid(),
|
|
4455
|
+
require_opencode(),
|
|
4456
|
+
require_openclaw(),
|
|
4457
|
+
// 2.1.14: Hermes Agent (Nous Research) — Python/uv based, macOS/Linux/WSL2 only.
|
|
4458
|
+
require_hermes(),
|
|
4459
|
+
require_env_config()
|
|
4460
|
+
];
|
|
4461
|
+
}
|
|
4462
|
+
});
|
|
4463
|
+
|
|
4464
|
+
// src/webui/configure-worker.js
|
|
4465
|
+
process.on("message", (msg) => {
|
|
4466
|
+
const TOOLS = require_tools();
|
|
4467
|
+
const { writeEnvToShell, removeEnvFromShell } = require_shell();
|
|
4468
|
+
const tool = TOOLS.find((t) => t.id === msg.toolId);
|
|
4469
|
+
if (!tool) {
|
|
4470
|
+
process.send({ type: "error", message: "\u672A\u77E5\u5DE5\u5177: " + msg.toolId });
|
|
4471
|
+
process.exit(1);
|
|
4472
|
+
}
|
|
4473
|
+
const stripAnsi = /* @__PURE__ */ __name((s) => String(s).replace(/\x1b\[[0-9;]*m/g, ""), "stripAnsi");
|
|
4474
|
+
console.log = (...args) => {
|
|
4475
|
+
const text = stripAnsi(args.join(" ")).trim();
|
|
4476
|
+
if (text) process.send({ type: "progress", message: text });
|
|
4477
|
+
};
|
|
4478
|
+
console.warn = console.log;
|
|
4479
|
+
try {
|
|
4480
|
+
const result = tool.configure(
|
|
4481
|
+
msg.apiKey,
|
|
4482
|
+
msg.baseUrlAnthropic,
|
|
4483
|
+
msg.baseUrlOpenAI,
|
|
4484
|
+
msg.primaryModel,
|
|
4485
|
+
msg.allModelIds
|
|
4486
|
+
);
|
|
4487
|
+
if (result.envVars && Object.keys(result.envVars).length > 0) {
|
|
4488
|
+
writeEnvToShell(result.envVars);
|
|
4489
|
+
process.send({ type: "progress", message: "\u5DF2\u5199\u5165\u73AF\u5883\u53D8\u91CF\u5230 shell \u914D\u7F6E" });
|
|
4490
|
+
}
|
|
4491
|
+
if (msg.toolId !== "env-config") {
|
|
4492
|
+
try {
|
|
4493
|
+
removeEnvFromShell(["ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL", "OPENAI_API_KEY", "OPENAI_BASE_URL"]);
|
|
4494
|
+
} catch {
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
4497
|
+
process.send({
|
|
4498
|
+
type: "result",
|
|
4499
|
+
status: result.manual ? "manual" : result.warning ? "warning" : "ok",
|
|
4500
|
+
message: result.manual ? `${tool.name} \u9700\u8981\u624B\u52A8\u5B8C\u6210\u914D\u7F6E` : result.warning ? result.warning : `${tool.name} \u914D\u7F6E\u6210\u529F`,
|
|
4501
|
+
file: result.file || null,
|
|
4502
|
+
hot: result.hot || false,
|
|
4503
|
+
steps: result.steps || null,
|
|
4504
|
+
dashboardUrl: result.dashboardUrl || null
|
|
4505
|
+
});
|
|
4506
|
+
} catch (e) {
|
|
4507
|
+
process.send({ type: "error", message: e.message });
|
|
4508
|
+
}
|
|
4509
|
+
process.exit(0);
|
|
4510
|
+
});
|