@openhoo/hoopilot 0.9.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +233 -128
- package/dist/{chunk-7GSQVYYT.js → chunk-JU6F5L34.js} +9 -5
- package/dist/chunk-JU6F5L34.js.map +1 -0
- package/dist/cli.js +93 -26
- package/dist/cli.js.map +1 -1
- package/dist/codexx.js +1 -1
- package/dist/index.cjs +57 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +57 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-7GSQVYYT.js.map +0 -1
package/dist/codexx.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -2694,7 +2694,8 @@ var IS_STANDALONE_BINARY = BAKED_VERSION !== void 0;
|
|
|
2694
2694
|
// src/server.ts
|
|
2695
2695
|
var DEFAULT_HOST = "127.0.0.1";
|
|
2696
2696
|
var DEFAULT_PORT = 4141;
|
|
2697
|
-
var FORBIDDEN_BROWSER_ORIGIN_MESSAGE = "
|
|
2697
|
+
var FORBIDDEN_BROWSER_ORIGIN_MESSAGE = "Cross-origin browser requests are blocked unless the Origin is loopback or listed in HOOPILOT_ALLOWED_ORIGINS.";
|
|
2698
|
+
var WELL_KNOWN_DEMO_API_KEYS = /* @__PURE__ */ new Set(["local-key"]);
|
|
2698
2699
|
var INVALID_JSON_MESSAGE = "Request body must be valid JSON.";
|
|
2699
2700
|
var JSON_OBJECT_MESSAGE = "Request body must be a JSON object.";
|
|
2700
2701
|
var MAX_REQUEST_BODY_BYTES = 16 * 1024 * 1024;
|
|
@@ -2710,6 +2711,7 @@ var RequestBodyTooLargeError = class extends Error {
|
|
|
2710
2711
|
function createHoopilotHandler(options = {}) {
|
|
2711
2712
|
const client = new CopilotClient(options);
|
|
2712
2713
|
const apiKey = options.apiKey ?? envValue(options.env?.HOOPILOT_API_KEY);
|
|
2714
|
+
const allowedOrigins = parseAllowedOrigins(options.env);
|
|
2713
2715
|
const logger = serverLogger(options);
|
|
2714
2716
|
const metrics = options.metrics ?? new MetricsRegistry();
|
|
2715
2717
|
const readUsage = createUsageReader(client, metrics);
|
|
@@ -2729,7 +2731,10 @@ function createHoopilotHandler(options = {}) {
|
|
|
2729
2731
|
route
|
|
2730
2732
|
});
|
|
2731
2733
|
metrics.startRequest();
|
|
2734
|
+
const origin = request.headers.get("origin")?.trim() || void 0;
|
|
2735
|
+
const corsOrigin = resolveCorsAllowOrigin(origin, allowedOrigins);
|
|
2732
2736
|
const finish = (response) => finishResponse(response, {
|
|
2737
|
+
corsOrigin,
|
|
2733
2738
|
logger: requestLogger,
|
|
2734
2739
|
method: request.method,
|
|
2735
2740
|
metrics,
|
|
@@ -2739,11 +2744,11 @@ function createHoopilotHandler(options = {}) {
|
|
|
2739
2744
|
closeConnection: bufferProxyBodies,
|
|
2740
2745
|
trackStreamingBody: !bufferProxyBodies
|
|
2741
2746
|
});
|
|
2742
|
-
const browserOrigin = forbiddenBrowserOrigin(request,
|
|
2747
|
+
const browserOrigin = forbiddenBrowserOrigin(origin, request, allowedOrigins);
|
|
2743
2748
|
if (browserOrigin) {
|
|
2744
2749
|
requestLogger.warn(
|
|
2745
2750
|
{ event: "http.request.forbidden_origin", origin: browserOrigin },
|
|
2746
|
-
"blocked
|
|
2751
|
+
"blocked cross-origin browser request"
|
|
2747
2752
|
);
|
|
2748
2753
|
return finish(jsonError(403, "forbidden_origin", FORBIDDEN_BROWSER_ORIGIN_MESSAGE));
|
|
2749
2754
|
}
|
|
@@ -2869,10 +2874,17 @@ function startHoopilotServer(options = {}) {
|
|
|
2869
2874
|
const port = normalizeServerPort(options.port ?? envValue(options.env?.PORT) ?? DEFAULT_PORT);
|
|
2870
2875
|
const apiKey = options.apiKey ?? envValue(options.env?.HOOPILOT_API_KEY);
|
|
2871
2876
|
const allowUnauthenticated = options.allowUnauthenticated ?? envValue(options.env?.HOOPILOT_ALLOW_UNAUTHENTICATED) === "1";
|
|
2872
|
-
if (!isLoopbackHost(host)
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2877
|
+
if (!isLoopbackHost(host)) {
|
|
2878
|
+
if (!apiKey && !allowUnauthenticated) {
|
|
2879
|
+
throw new Error(
|
|
2880
|
+
"Refusing to listen on a non-loopback host without HOOPILOT_API_KEY. Set an API key or pass --allow-unauthenticated."
|
|
2881
|
+
);
|
|
2882
|
+
}
|
|
2883
|
+
if (apiKey && isWellKnownDemoApiKey(apiKey)) {
|
|
2884
|
+
throw new Error(
|
|
2885
|
+
"Refusing to listen on a non-loopback host with a well-known demo HOOPILOT_API_KEY. Set a strong, unique API key."
|
|
2886
|
+
);
|
|
2887
|
+
}
|
|
2876
2888
|
}
|
|
2877
2889
|
const server = Bun.serve({
|
|
2878
2890
|
fetch: createHoopilotHandler({
|
|
@@ -3185,7 +3197,6 @@ function corsHeaders() {
|
|
|
3185
3197
|
return {
|
|
3186
3198
|
"access-control-allow-headers": "anthropic-beta, anthropic-dangerous-direct-browser-access, anthropic-version, authorization, content-type, x-api-key, x-request-id",
|
|
3187
3199
|
"access-control-allow-methods": "GET, POST, OPTIONS",
|
|
3188
|
-
"access-control-allow-origin": "*",
|
|
3189
3200
|
"access-control-expose-headers": "x-request-id"
|
|
3190
3201
|
};
|
|
3191
3202
|
}
|
|
@@ -3197,17 +3208,34 @@ function isAuthorized(request, apiKey) {
|
|
|
3197
3208
|
const bearer = authorization.match(/^Bearer\s+(.+)$/i)?.[1];
|
|
3198
3209
|
return bearer === apiKey || request.headers.get("x-api-key") === apiKey;
|
|
3199
3210
|
}
|
|
3200
|
-
function forbiddenBrowserOrigin(request,
|
|
3201
|
-
if (apiKey) {
|
|
3202
|
-
return void 0;
|
|
3203
|
-
}
|
|
3204
|
-
const origin = request.headers.get("origin")?.trim();
|
|
3211
|
+
function forbiddenBrowserOrigin(origin, request, allowedOrigins) {
|
|
3205
3212
|
if (origin) {
|
|
3206
|
-
return
|
|
3213
|
+
return isAllowedOrigin(origin, allowedOrigins) ? void 0 : origin;
|
|
3207
3214
|
}
|
|
3208
3215
|
const fetchSite = request.headers.get("sec-fetch-site")?.toLowerCase();
|
|
3209
3216
|
return fetchSite === "cross-site" ? "cross-site" : void 0;
|
|
3210
3217
|
}
|
|
3218
|
+
function parseAllowedOrigins(env) {
|
|
3219
|
+
const raw = envValue(env?.HOOPILOT_ALLOWED_ORIGINS);
|
|
3220
|
+
if (!raw) {
|
|
3221
|
+
return /* @__PURE__ */ new Set();
|
|
3222
|
+
}
|
|
3223
|
+
return new Set(
|
|
3224
|
+
raw.split(",").map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0)
|
|
3225
|
+
);
|
|
3226
|
+
}
|
|
3227
|
+
function isAllowedOrigin(origin, allowedOrigins) {
|
|
3228
|
+
return isLoopbackOrigin(origin) || allowedOrigins.has(origin.toLowerCase());
|
|
3229
|
+
}
|
|
3230
|
+
function resolveCorsAllowOrigin(origin, allowedOrigins) {
|
|
3231
|
+
if (!origin) {
|
|
3232
|
+
return "*";
|
|
3233
|
+
}
|
|
3234
|
+
return isAllowedOrigin(origin, allowedOrigins) ? origin : void 0;
|
|
3235
|
+
}
|
|
3236
|
+
function isWellKnownDemoApiKey(apiKey) {
|
|
3237
|
+
return WELL_KNOWN_DEMO_API_KEYS.has(apiKey.trim().toLowerCase());
|
|
3238
|
+
}
|
|
3211
3239
|
function isUpstreamAuthStatus(status) {
|
|
3212
3240
|
return status === 401 || status === 403;
|
|
3213
3241
|
}
|
|
@@ -3267,7 +3295,12 @@ function shouldBufferProxyBodies(mode) {
|
|
|
3267
3295
|
return process.platform === "win32" && IS_STANDALONE_BINARY;
|
|
3268
3296
|
}
|
|
3269
3297
|
function finishResponse(response, options) {
|
|
3270
|
-
const withRequestId = responseWithRequestId(
|
|
3298
|
+
const withRequestId = responseWithRequestId(
|
|
3299
|
+
response,
|
|
3300
|
+
options.requestId,
|
|
3301
|
+
options.closeConnection,
|
|
3302
|
+
options.corsOrigin
|
|
3303
|
+
);
|
|
3271
3304
|
const stream = isStreamingResponse(withRequestId);
|
|
3272
3305
|
const status = withRequestId.status;
|
|
3273
3306
|
const complete = () => {
|
|
@@ -3285,9 +3318,17 @@ function finishResponse(response, options) {
|
|
|
3285
3318
|
complete();
|
|
3286
3319
|
return withRequestId;
|
|
3287
3320
|
}
|
|
3288
|
-
function responseWithRequestId(response, requestId, closeConnection) {
|
|
3321
|
+
function responseWithRequestId(response, requestId, closeConnection, corsOrigin) {
|
|
3289
3322
|
const headers = new Headers(response.headers);
|
|
3290
3323
|
headers.set("x-request-id", requestId);
|
|
3324
|
+
if (corsOrigin) {
|
|
3325
|
+
headers.set("access-control-allow-origin", corsOrigin);
|
|
3326
|
+
if (corsOrigin !== "*") {
|
|
3327
|
+
headers.append("vary", "Origin");
|
|
3328
|
+
}
|
|
3329
|
+
} else {
|
|
3330
|
+
headers.delete("access-control-allow-origin");
|
|
3331
|
+
}
|
|
3291
3332
|
if (closeConnection) {
|
|
3292
3333
|
headers.set("connection", "close");
|
|
3293
3334
|
}
|