@openhoo/hoopilot 0.10.0 → 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 +222 -149
- package/dist/{chunk-7GSQVYYT.js → chunk-JU6F5L34.js} +9 -5
- package/dist/chunk-JU6F5L34.js.map +1 -0
- package/dist/cli.js +58 -17
- 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/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
main,
|
|
7
7
|
trimTrailingSlash,
|
|
8
8
|
truncatedResponseText
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-JU6F5L34.js";
|
|
10
10
|
|
|
11
11
|
// src/cli.ts
|
|
12
12
|
import { spawn } from "child_process";
|
|
@@ -2107,7 +2107,8 @@ async function getVersion() {
|
|
|
2107
2107
|
// src/server.ts
|
|
2108
2108
|
var DEFAULT_HOST = "127.0.0.1";
|
|
2109
2109
|
var DEFAULT_PORT = 4141;
|
|
2110
|
-
var FORBIDDEN_BROWSER_ORIGIN_MESSAGE = "
|
|
2110
|
+
var FORBIDDEN_BROWSER_ORIGIN_MESSAGE = "Cross-origin browser requests are blocked unless the Origin is loopback or listed in HOOPILOT_ALLOWED_ORIGINS.";
|
|
2111
|
+
var WELL_KNOWN_DEMO_API_KEYS = /* @__PURE__ */ new Set(["local-key"]);
|
|
2111
2112
|
var INVALID_JSON_MESSAGE = "Request body must be valid JSON.";
|
|
2112
2113
|
var JSON_OBJECT_MESSAGE = "Request body must be a JSON object.";
|
|
2113
2114
|
var MAX_REQUEST_BODY_BYTES = 16 * 1024 * 1024;
|
|
@@ -2123,6 +2124,7 @@ var RequestBodyTooLargeError = class extends Error {
|
|
|
2123
2124
|
function createHoopilotHandler(options = {}) {
|
|
2124
2125
|
const client = new CopilotClient(options);
|
|
2125
2126
|
const apiKey = options.apiKey ?? envValue(options.env?.HOOPILOT_API_KEY);
|
|
2127
|
+
const allowedOrigins = parseAllowedOrigins(options.env);
|
|
2126
2128
|
const logger = serverLogger(options);
|
|
2127
2129
|
const metrics = options.metrics ?? new MetricsRegistry();
|
|
2128
2130
|
const readUsage = createUsageReader(client, metrics);
|
|
@@ -2142,7 +2144,10 @@ function createHoopilotHandler(options = {}) {
|
|
|
2142
2144
|
route
|
|
2143
2145
|
});
|
|
2144
2146
|
metrics.startRequest();
|
|
2147
|
+
const origin = request.headers.get("origin")?.trim() || void 0;
|
|
2148
|
+
const corsOrigin = resolveCorsAllowOrigin(origin, allowedOrigins);
|
|
2145
2149
|
const finish = (response) => finishResponse(response, {
|
|
2150
|
+
corsOrigin,
|
|
2146
2151
|
logger: requestLogger,
|
|
2147
2152
|
method: request.method,
|
|
2148
2153
|
metrics,
|
|
@@ -2152,11 +2157,11 @@ function createHoopilotHandler(options = {}) {
|
|
|
2152
2157
|
closeConnection: bufferProxyBodies,
|
|
2153
2158
|
trackStreamingBody: !bufferProxyBodies
|
|
2154
2159
|
});
|
|
2155
|
-
const browserOrigin = forbiddenBrowserOrigin(request,
|
|
2160
|
+
const browserOrigin = forbiddenBrowserOrigin(origin, request, allowedOrigins);
|
|
2156
2161
|
if (browserOrigin) {
|
|
2157
2162
|
requestLogger.warn(
|
|
2158
2163
|
{ event: "http.request.forbidden_origin", origin: browserOrigin },
|
|
2159
|
-
"blocked
|
|
2164
|
+
"blocked cross-origin browser request"
|
|
2160
2165
|
);
|
|
2161
2166
|
return finish(jsonError(403, "forbidden_origin", FORBIDDEN_BROWSER_ORIGIN_MESSAGE));
|
|
2162
2167
|
}
|
|
@@ -2282,10 +2287,17 @@ function startHoopilotServer(options = {}) {
|
|
|
2282
2287
|
const port = normalizeServerPort(options.port ?? envValue(options.env?.PORT) ?? DEFAULT_PORT);
|
|
2283
2288
|
const apiKey = options.apiKey ?? envValue(options.env?.HOOPILOT_API_KEY);
|
|
2284
2289
|
const allowUnauthenticated = options.allowUnauthenticated ?? envValue(options.env?.HOOPILOT_ALLOW_UNAUTHENTICATED) === "1";
|
|
2285
|
-
if (!isLoopbackHost(host)
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2290
|
+
if (!isLoopbackHost(host)) {
|
|
2291
|
+
if (!apiKey && !allowUnauthenticated) {
|
|
2292
|
+
throw new Error(
|
|
2293
|
+
"Refusing to listen on a non-loopback host without HOOPILOT_API_KEY. Set an API key or pass --allow-unauthenticated."
|
|
2294
|
+
);
|
|
2295
|
+
}
|
|
2296
|
+
if (apiKey && isWellKnownDemoApiKey(apiKey)) {
|
|
2297
|
+
throw new Error(
|
|
2298
|
+
"Refusing to listen on a non-loopback host with a well-known demo HOOPILOT_API_KEY. Set a strong, unique API key."
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2289
2301
|
}
|
|
2290
2302
|
const server = Bun.serve({
|
|
2291
2303
|
fetch: createHoopilotHandler({
|
|
@@ -2598,7 +2610,6 @@ function corsHeaders() {
|
|
|
2598
2610
|
return {
|
|
2599
2611
|
"access-control-allow-headers": "anthropic-beta, anthropic-dangerous-direct-browser-access, anthropic-version, authorization, content-type, x-api-key, x-request-id",
|
|
2600
2612
|
"access-control-allow-methods": "GET, POST, OPTIONS",
|
|
2601
|
-
"access-control-allow-origin": "*",
|
|
2602
2613
|
"access-control-expose-headers": "x-request-id"
|
|
2603
2614
|
};
|
|
2604
2615
|
}
|
|
@@ -2610,17 +2621,34 @@ function isAuthorized(request, apiKey) {
|
|
|
2610
2621
|
const bearer = authorization.match(/^Bearer\s+(.+)$/i)?.[1];
|
|
2611
2622
|
return bearer === apiKey || request.headers.get("x-api-key") === apiKey;
|
|
2612
2623
|
}
|
|
2613
|
-
function forbiddenBrowserOrigin(request,
|
|
2614
|
-
if (apiKey) {
|
|
2615
|
-
return void 0;
|
|
2616
|
-
}
|
|
2617
|
-
const origin = request.headers.get("origin")?.trim();
|
|
2624
|
+
function forbiddenBrowserOrigin(origin, request, allowedOrigins) {
|
|
2618
2625
|
if (origin) {
|
|
2619
|
-
return
|
|
2626
|
+
return isAllowedOrigin(origin, allowedOrigins) ? void 0 : origin;
|
|
2620
2627
|
}
|
|
2621
2628
|
const fetchSite = request.headers.get("sec-fetch-site")?.toLowerCase();
|
|
2622
2629
|
return fetchSite === "cross-site" ? "cross-site" : void 0;
|
|
2623
2630
|
}
|
|
2631
|
+
function parseAllowedOrigins(env) {
|
|
2632
|
+
const raw = envValue(env?.HOOPILOT_ALLOWED_ORIGINS);
|
|
2633
|
+
if (!raw) {
|
|
2634
|
+
return /* @__PURE__ */ new Set();
|
|
2635
|
+
}
|
|
2636
|
+
return new Set(
|
|
2637
|
+
raw.split(",").map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0)
|
|
2638
|
+
);
|
|
2639
|
+
}
|
|
2640
|
+
function isAllowedOrigin(origin, allowedOrigins) {
|
|
2641
|
+
return isLoopbackOrigin(origin) || allowedOrigins.has(origin.toLowerCase());
|
|
2642
|
+
}
|
|
2643
|
+
function resolveCorsAllowOrigin(origin, allowedOrigins) {
|
|
2644
|
+
if (!origin) {
|
|
2645
|
+
return "*";
|
|
2646
|
+
}
|
|
2647
|
+
return isAllowedOrigin(origin, allowedOrigins) ? origin : void 0;
|
|
2648
|
+
}
|
|
2649
|
+
function isWellKnownDemoApiKey(apiKey) {
|
|
2650
|
+
return WELL_KNOWN_DEMO_API_KEYS.has(apiKey.trim().toLowerCase());
|
|
2651
|
+
}
|
|
2624
2652
|
function isUpstreamAuthStatus(status) {
|
|
2625
2653
|
return status === 401 || status === 403;
|
|
2626
2654
|
}
|
|
@@ -2680,7 +2708,12 @@ function shouldBufferProxyBodies(mode) {
|
|
|
2680
2708
|
return process.platform === "win32" && IS_STANDALONE_BINARY;
|
|
2681
2709
|
}
|
|
2682
2710
|
function finishResponse(response, options) {
|
|
2683
|
-
const withRequestId = responseWithRequestId(
|
|
2711
|
+
const withRequestId = responseWithRequestId(
|
|
2712
|
+
response,
|
|
2713
|
+
options.requestId,
|
|
2714
|
+
options.closeConnection,
|
|
2715
|
+
options.corsOrigin
|
|
2716
|
+
);
|
|
2684
2717
|
const stream = isStreamingResponse(withRequestId);
|
|
2685
2718
|
const status = withRequestId.status;
|
|
2686
2719
|
const complete = () => {
|
|
@@ -2698,9 +2731,17 @@ function finishResponse(response, options) {
|
|
|
2698
2731
|
complete();
|
|
2699
2732
|
return withRequestId;
|
|
2700
2733
|
}
|
|
2701
|
-
function responseWithRequestId(response, requestId, closeConnection) {
|
|
2734
|
+
function responseWithRequestId(response, requestId, closeConnection, corsOrigin) {
|
|
2702
2735
|
const headers = new Headers(response.headers);
|
|
2703
2736
|
headers.set("x-request-id", requestId);
|
|
2737
|
+
if (corsOrigin) {
|
|
2738
|
+
headers.set("access-control-allow-origin", corsOrigin);
|
|
2739
|
+
if (corsOrigin !== "*") {
|
|
2740
|
+
headers.append("vary", "Origin");
|
|
2741
|
+
}
|
|
2742
|
+
} else {
|
|
2743
|
+
headers.delete("access-control-allow-origin");
|
|
2744
|
+
}
|
|
2704
2745
|
if (closeConnection) {
|
|
2705
2746
|
headers.set("connection", "close");
|
|
2706
2747
|
}
|