@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/index.js
CHANGED
|
@@ -2618,7 +2618,8 @@ var IS_STANDALONE_BINARY = BAKED_VERSION !== void 0;
|
|
|
2618
2618
|
// src/server.ts
|
|
2619
2619
|
var DEFAULT_HOST = "127.0.0.1";
|
|
2620
2620
|
var DEFAULT_PORT = 4141;
|
|
2621
|
-
var FORBIDDEN_BROWSER_ORIGIN_MESSAGE = "
|
|
2621
|
+
var FORBIDDEN_BROWSER_ORIGIN_MESSAGE = "Cross-origin browser requests are blocked unless the Origin is loopback or listed in HOOPILOT_ALLOWED_ORIGINS.";
|
|
2622
|
+
var WELL_KNOWN_DEMO_API_KEYS = /* @__PURE__ */ new Set(["local-key"]);
|
|
2622
2623
|
var INVALID_JSON_MESSAGE = "Request body must be valid JSON.";
|
|
2623
2624
|
var JSON_OBJECT_MESSAGE = "Request body must be a JSON object.";
|
|
2624
2625
|
var MAX_REQUEST_BODY_BYTES = 16 * 1024 * 1024;
|
|
@@ -2634,6 +2635,7 @@ var RequestBodyTooLargeError = class extends Error {
|
|
|
2634
2635
|
function createHoopilotHandler(options = {}) {
|
|
2635
2636
|
const client = new CopilotClient(options);
|
|
2636
2637
|
const apiKey = options.apiKey ?? envValue(options.env?.HOOPILOT_API_KEY);
|
|
2638
|
+
const allowedOrigins = parseAllowedOrigins(options.env);
|
|
2637
2639
|
const logger = serverLogger(options);
|
|
2638
2640
|
const metrics = options.metrics ?? new MetricsRegistry();
|
|
2639
2641
|
const readUsage = createUsageReader(client, metrics);
|
|
@@ -2653,7 +2655,10 @@ function createHoopilotHandler(options = {}) {
|
|
|
2653
2655
|
route
|
|
2654
2656
|
});
|
|
2655
2657
|
metrics.startRequest();
|
|
2658
|
+
const origin = request.headers.get("origin")?.trim() || void 0;
|
|
2659
|
+
const corsOrigin = resolveCorsAllowOrigin(origin, allowedOrigins);
|
|
2656
2660
|
const finish = (response) => finishResponse(response, {
|
|
2661
|
+
corsOrigin,
|
|
2657
2662
|
logger: requestLogger,
|
|
2658
2663
|
method: request.method,
|
|
2659
2664
|
metrics,
|
|
@@ -2663,11 +2668,11 @@ function createHoopilotHandler(options = {}) {
|
|
|
2663
2668
|
closeConnection: bufferProxyBodies,
|
|
2664
2669
|
trackStreamingBody: !bufferProxyBodies
|
|
2665
2670
|
});
|
|
2666
|
-
const browserOrigin = forbiddenBrowserOrigin(request,
|
|
2671
|
+
const browserOrigin = forbiddenBrowserOrigin(origin, request, allowedOrigins);
|
|
2667
2672
|
if (browserOrigin) {
|
|
2668
2673
|
requestLogger.warn(
|
|
2669
2674
|
{ event: "http.request.forbidden_origin", origin: browserOrigin },
|
|
2670
|
-
"blocked
|
|
2675
|
+
"blocked cross-origin browser request"
|
|
2671
2676
|
);
|
|
2672
2677
|
return finish(jsonError(403, "forbidden_origin", FORBIDDEN_BROWSER_ORIGIN_MESSAGE));
|
|
2673
2678
|
}
|
|
@@ -2793,10 +2798,17 @@ function startHoopilotServer(options = {}) {
|
|
|
2793
2798
|
const port = normalizeServerPort(options.port ?? envValue(options.env?.PORT) ?? DEFAULT_PORT);
|
|
2794
2799
|
const apiKey = options.apiKey ?? envValue(options.env?.HOOPILOT_API_KEY);
|
|
2795
2800
|
const allowUnauthenticated = options.allowUnauthenticated ?? envValue(options.env?.HOOPILOT_ALLOW_UNAUTHENTICATED) === "1";
|
|
2796
|
-
if (!isLoopbackHost(host)
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2801
|
+
if (!isLoopbackHost(host)) {
|
|
2802
|
+
if (!apiKey && !allowUnauthenticated) {
|
|
2803
|
+
throw new Error(
|
|
2804
|
+
"Refusing to listen on a non-loopback host without HOOPILOT_API_KEY. Set an API key or pass --allow-unauthenticated."
|
|
2805
|
+
);
|
|
2806
|
+
}
|
|
2807
|
+
if (apiKey && isWellKnownDemoApiKey(apiKey)) {
|
|
2808
|
+
throw new Error(
|
|
2809
|
+
"Refusing to listen on a non-loopback host with a well-known demo HOOPILOT_API_KEY. Set a strong, unique API key."
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2800
2812
|
}
|
|
2801
2813
|
const server = Bun.serve({
|
|
2802
2814
|
fetch: createHoopilotHandler({
|
|
@@ -3109,7 +3121,6 @@ function corsHeaders() {
|
|
|
3109
3121
|
return {
|
|
3110
3122
|
"access-control-allow-headers": "anthropic-beta, anthropic-dangerous-direct-browser-access, anthropic-version, authorization, content-type, x-api-key, x-request-id",
|
|
3111
3123
|
"access-control-allow-methods": "GET, POST, OPTIONS",
|
|
3112
|
-
"access-control-allow-origin": "*",
|
|
3113
3124
|
"access-control-expose-headers": "x-request-id"
|
|
3114
3125
|
};
|
|
3115
3126
|
}
|
|
@@ -3121,17 +3132,34 @@ function isAuthorized(request, apiKey) {
|
|
|
3121
3132
|
const bearer = authorization.match(/^Bearer\s+(.+)$/i)?.[1];
|
|
3122
3133
|
return bearer === apiKey || request.headers.get("x-api-key") === apiKey;
|
|
3123
3134
|
}
|
|
3124
|
-
function forbiddenBrowserOrigin(request,
|
|
3125
|
-
if (apiKey) {
|
|
3126
|
-
return void 0;
|
|
3127
|
-
}
|
|
3128
|
-
const origin = request.headers.get("origin")?.trim();
|
|
3135
|
+
function forbiddenBrowserOrigin(origin, request, allowedOrigins) {
|
|
3129
3136
|
if (origin) {
|
|
3130
|
-
return
|
|
3137
|
+
return isAllowedOrigin(origin, allowedOrigins) ? void 0 : origin;
|
|
3131
3138
|
}
|
|
3132
3139
|
const fetchSite = request.headers.get("sec-fetch-site")?.toLowerCase();
|
|
3133
3140
|
return fetchSite === "cross-site" ? "cross-site" : void 0;
|
|
3134
3141
|
}
|
|
3142
|
+
function parseAllowedOrigins(env) {
|
|
3143
|
+
const raw = envValue(env?.HOOPILOT_ALLOWED_ORIGINS);
|
|
3144
|
+
if (!raw) {
|
|
3145
|
+
return /* @__PURE__ */ new Set();
|
|
3146
|
+
}
|
|
3147
|
+
return new Set(
|
|
3148
|
+
raw.split(",").map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0)
|
|
3149
|
+
);
|
|
3150
|
+
}
|
|
3151
|
+
function isAllowedOrigin(origin, allowedOrigins) {
|
|
3152
|
+
return isLoopbackOrigin(origin) || allowedOrigins.has(origin.toLowerCase());
|
|
3153
|
+
}
|
|
3154
|
+
function resolveCorsAllowOrigin(origin, allowedOrigins) {
|
|
3155
|
+
if (!origin) {
|
|
3156
|
+
return "*";
|
|
3157
|
+
}
|
|
3158
|
+
return isAllowedOrigin(origin, allowedOrigins) ? origin : void 0;
|
|
3159
|
+
}
|
|
3160
|
+
function isWellKnownDemoApiKey(apiKey) {
|
|
3161
|
+
return WELL_KNOWN_DEMO_API_KEYS.has(apiKey.trim().toLowerCase());
|
|
3162
|
+
}
|
|
3135
3163
|
function isUpstreamAuthStatus(status) {
|
|
3136
3164
|
return status === 401 || status === 403;
|
|
3137
3165
|
}
|
|
@@ -3191,7 +3219,12 @@ function shouldBufferProxyBodies(mode) {
|
|
|
3191
3219
|
return process.platform === "win32" && IS_STANDALONE_BINARY;
|
|
3192
3220
|
}
|
|
3193
3221
|
function finishResponse(response, options) {
|
|
3194
|
-
const withRequestId = responseWithRequestId(
|
|
3222
|
+
const withRequestId = responseWithRequestId(
|
|
3223
|
+
response,
|
|
3224
|
+
options.requestId,
|
|
3225
|
+
options.closeConnection,
|
|
3226
|
+
options.corsOrigin
|
|
3227
|
+
);
|
|
3195
3228
|
const stream = isStreamingResponse(withRequestId);
|
|
3196
3229
|
const status = withRequestId.status;
|
|
3197
3230
|
const complete = () => {
|
|
@@ -3209,9 +3242,17 @@ function finishResponse(response, options) {
|
|
|
3209
3242
|
complete();
|
|
3210
3243
|
return withRequestId;
|
|
3211
3244
|
}
|
|
3212
|
-
function responseWithRequestId(response, requestId, closeConnection) {
|
|
3245
|
+
function responseWithRequestId(response, requestId, closeConnection, corsOrigin) {
|
|
3213
3246
|
const headers = new Headers(response.headers);
|
|
3214
3247
|
headers.set("x-request-id", requestId);
|
|
3248
|
+
if (corsOrigin) {
|
|
3249
|
+
headers.set("access-control-allow-origin", corsOrigin);
|
|
3250
|
+
if (corsOrigin !== "*") {
|
|
3251
|
+
headers.append("vary", "Origin");
|
|
3252
|
+
}
|
|
3253
|
+
} else {
|
|
3254
|
+
headers.delete("access-control-allow-origin");
|
|
3255
|
+
}
|
|
3215
3256
|
if (closeConnection) {
|
|
3216
3257
|
headers.set("connection", "close");
|
|
3217
3258
|
}
|