@openhoo/hoopilot 0.7.1 → 0.7.3
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 +6 -2
- package/dist/chunk-7GSQVYYT.js +221 -0
- package/dist/chunk-7GSQVYYT.js.map +1 -0
- package/dist/cli.js +473 -83
- package/dist/cli.js.map +1 -1
- package/dist/codexx.js +5 -161
- package/dist/codexx.js.map +1 -1
- package/dist/index.cjs +533 -155
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +532 -155
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -41,11 +41,11 @@ irm https://raw.githubusercontent.com/openhoo/hoopilot/main/scripts/install.ps1
|
|
|
41
41
|
The installer detects your OS, CPU architecture, and libc, downloads the matching binary, verifies its SHA-256 checksum, and installs it to `~/.local/bin` on Linux/macOS or `%LOCALAPPDATA%\Programs\hoopilot` on Windows. Override the location with `HOOPILOT_INSTALL_DIR`, or pin a version:
|
|
42
42
|
|
|
43
43
|
```powershell
|
|
44
|
-
curl -fsSL https://raw.githubusercontent.com/openhoo/hoopilot/main/scripts/install.sh | sh -s -- --version
|
|
44
|
+
curl -fsSL https://raw.githubusercontent.com/openhoo/hoopilot/main/scripts/install.sh | sh -s -- --version <version> --dir ~/bin
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
```powershell
|
|
48
|
-
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/openhoo/hoopilot/main/scripts/install.ps1))) -Version
|
|
48
|
+
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/openhoo/hoopilot/main/scripts/install.ps1))) -Version <version>
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
The standalone installer also installs a `codexx` wrapper next to `hoopilot`. Re-run the installer if `hoopilot` works but your shell does not recognize `codexx`; the installer stops the installed `hoopilot.exe` if needed and replaces the existing files in place.
|
|
@@ -184,6 +184,7 @@ Supported authentication-related settings:
|
|
|
184
184
|
- `HOOPILOT_GITHUB_DOMAIN`: GitHub domain override. Default: `github.com`.
|
|
185
185
|
- `COPILOT_API_BASE_URL`: upstream Copilot API base URL override. Default: `https://api.githubcopilot.com`.
|
|
186
186
|
- `HOOPILOT_GITHUB_API_BASE_URL`: GitHub REST API base URL used for the Copilot quota lookup. Default: `https://api.github.com`.
|
|
187
|
+
- `HOOPILOT_ALLOW_UNSAFE_UPSTREAM=1`: allow sending the stored OAuth token to nonstandard HTTPS Copilot/GitHub API hosts. Use only for trusted test or enterprise endpoints.
|
|
187
188
|
|
|
188
189
|
## Codex Auth Errors
|
|
189
190
|
|
|
@@ -212,6 +213,7 @@ If that returns `401 copilot_auth_error`, rerun `npx @openhoo/hoopilot login` an
|
|
|
212
213
|
|
|
213
214
|
```powershell
|
|
214
215
|
hoopilot [serve] [options]
|
|
216
|
+
hoopilot codexx [codex options] [prompt]
|
|
215
217
|
hoopilot login [options]
|
|
216
218
|
hoopilot models [options]
|
|
217
219
|
hoopilot usage [options]
|
|
@@ -221,6 +223,7 @@ Commands:
|
|
|
221
223
|
|
|
222
224
|
```txt
|
|
223
225
|
serve Start the proxy server (default)
|
|
226
|
+
codexx Run Codex through the local Hoopilot server
|
|
224
227
|
login Sign in through GitHub OAuth in a browser and verify Copilot access
|
|
225
228
|
models List available GitHub Copilot model IDs
|
|
226
229
|
usage Show GitHub Copilot quota and premium-request usage
|
|
@@ -233,6 +236,7 @@ Options:
|
|
|
233
236
|
-p, --port <port> Port to listen on. Default: 4141
|
|
234
237
|
--host <host> Host to listen on. Default: 127.0.0.1
|
|
235
238
|
--api-key <key> Require clients to send Authorization: Bearer <key> or x-api-key: <key>
|
|
239
|
+
--api-key-file <path> Read the local API key from a file instead of argv
|
|
236
240
|
--auth-file <path> OAuth credential store path
|
|
237
241
|
--copilot-api-base-url <url> Copilot API base URL override
|
|
238
242
|
--log-level <level> trace, debug, info, warn, error, fatal, or silent
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// src/codexx.ts
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { constants as osConstants } from "os";
|
|
4
|
+
|
|
5
|
+
// src/util.ts
|
|
6
|
+
function trimTrailingSlash(value) {
|
|
7
|
+
return value.replace(/\/+$/, "");
|
|
8
|
+
}
|
|
9
|
+
function envValue(value) {
|
|
10
|
+
const trimmed = value?.trim();
|
|
11
|
+
return trimmed ? trimmed : void 0;
|
|
12
|
+
}
|
|
13
|
+
function isTrustedTokenBaseUrl(rawUrl, allowedHttpsHosts, allowUnsafeHttps = false) {
|
|
14
|
+
const url = parseUrl(rawUrl);
|
|
15
|
+
if (!url) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (url.username || url.password || url.search || url.hash) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (url.pathname !== "" && url.pathname !== "/") {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (isLoopbackHttpUrl(url)) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
if (url.protocol !== "https:") {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const host = url.hostname.toLowerCase();
|
|
31
|
+
return allowedHttpsHosts.includes(host) || allowUnsafeHttps;
|
|
32
|
+
}
|
|
33
|
+
function parseUrl(rawUrl) {
|
|
34
|
+
let url;
|
|
35
|
+
try {
|
|
36
|
+
url = new URL(rawUrl);
|
|
37
|
+
} catch {
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
40
|
+
return url;
|
|
41
|
+
}
|
|
42
|
+
function isLoopbackHttpUrl(url) {
|
|
43
|
+
return url.protocol === "http:" && (url.hostname === "127.0.0.1" || url.hostname === "localhost" || url.hostname === "::1" || url.hostname === "[::1]");
|
|
44
|
+
}
|
|
45
|
+
async function truncatedResponseText(response, max = 500) {
|
|
46
|
+
const text = await response.text();
|
|
47
|
+
return text.slice(0, max);
|
|
48
|
+
}
|
|
49
|
+
function asRecord(value) {
|
|
50
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/codexx.ts
|
|
54
|
+
var DEFAULT_BASE_URL = "http://127.0.0.1:4141/v1";
|
|
55
|
+
var DEFAULT_API_KEY = "local-key";
|
|
56
|
+
var DEFAULT_CODEX_BIN = "codex";
|
|
57
|
+
var DEFAULT_MODEL = "gpt-5.5";
|
|
58
|
+
var DEFAULT_REASONING_EFFORT = "xhigh";
|
|
59
|
+
var PROXY_ENV_KEYS = [
|
|
60
|
+
"ALL_PROXY",
|
|
61
|
+
"HTTPS_PROXY",
|
|
62
|
+
"HTTP_PROXY",
|
|
63
|
+
"NO_PROXY",
|
|
64
|
+
"all_proxy",
|
|
65
|
+
"https_proxy",
|
|
66
|
+
"http_proxy",
|
|
67
|
+
"no_proxy"
|
|
68
|
+
];
|
|
69
|
+
function buildCodexxInvocation(argv, env = process.env) {
|
|
70
|
+
const baseUrl = envValue(env.CODEXX_BASE_URL) ?? DEFAULT_BASE_URL;
|
|
71
|
+
const apiKey = envValue(env.CODEXX_API_KEY) ?? envValue(env.HOOPILOT_API_KEY) ?? DEFAULT_API_KEY;
|
|
72
|
+
const command = envValue(env.CODEXX_CODEX_BIN) ?? DEFAULT_CODEX_BIN;
|
|
73
|
+
const model = envValue(env.CODEXX_MODEL) ?? DEFAULT_MODEL;
|
|
74
|
+
const reasoningEffort = envValue(env.CODEXX_MODEL_REASONING_EFFORT) ?? DEFAULT_REASONING_EFFORT;
|
|
75
|
+
const providerConfig = [
|
|
76
|
+
'{ name = "Hoopilot"',
|
|
77
|
+
`base_url = ${JSON.stringify(baseUrl)}`,
|
|
78
|
+
'env_key = "OPENAI_API_KEY"',
|
|
79
|
+
'wire_api = "responses"',
|
|
80
|
+
"supports_websockets = false }"
|
|
81
|
+
].join(", ");
|
|
82
|
+
return {
|
|
83
|
+
args: [
|
|
84
|
+
"--disable",
|
|
85
|
+
"network_proxy",
|
|
86
|
+
"-c",
|
|
87
|
+
'model_provider="hoopilot"',
|
|
88
|
+
"-c",
|
|
89
|
+
`model_providers.hoopilot=${providerConfig}`,
|
|
90
|
+
"-m",
|
|
91
|
+
model,
|
|
92
|
+
"-c",
|
|
93
|
+
`model_reasoning_effort=${JSON.stringify(reasoningEffort)}`,
|
|
94
|
+
...argv
|
|
95
|
+
],
|
|
96
|
+
baseUrl,
|
|
97
|
+
command,
|
|
98
|
+
env: withoutProxyEnv({
|
|
99
|
+
...env,
|
|
100
|
+
OPENAI_API_KEY: apiKey
|
|
101
|
+
}),
|
|
102
|
+
model
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function withoutProxyEnv(env) {
|
|
106
|
+
const next = { ...env };
|
|
107
|
+
for (const key of PROXY_ENV_KEYS) {
|
|
108
|
+
delete next[key];
|
|
109
|
+
}
|
|
110
|
+
return next;
|
|
111
|
+
}
|
|
112
|
+
async function main(argv = Bun.argv.slice(2), env = process.env) {
|
|
113
|
+
if (argv.length === 1 && (argv[0] === "--help" || argv[0] === "-h")) {
|
|
114
|
+
console.log(helpText());
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const invocation = buildCodexxInvocation(argv, env);
|
|
118
|
+
if (env.CODEXX_SKIP_MODEL_PREFLIGHT !== "1") {
|
|
119
|
+
await verifyCodexxModel(invocation);
|
|
120
|
+
}
|
|
121
|
+
const child = spawn(invocation.command, invocation.args, {
|
|
122
|
+
env: invocation.env,
|
|
123
|
+
shell: process.platform === "win32",
|
|
124
|
+
stdio: "inherit"
|
|
125
|
+
});
|
|
126
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
127
|
+
child.once("error", reject);
|
|
128
|
+
child.once("exit", (code, signal) => {
|
|
129
|
+
if (typeof code === "number") {
|
|
130
|
+
resolve(code);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
resolve(signal ? 128 + signalNumber(signal) : 1);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
process.exitCode = exitCode;
|
|
137
|
+
}
|
|
138
|
+
async function verifyCodexxModel(invocation, fetcher = fetch) {
|
|
139
|
+
const modelsUrl = `${invocation.baseUrl.replace(/\/+$/, "")}/models`;
|
|
140
|
+
let response;
|
|
141
|
+
try {
|
|
142
|
+
response = await fetcher(modelsUrl, {
|
|
143
|
+
headers: {
|
|
144
|
+
accept: "application/json",
|
|
145
|
+
authorization: `Bearer ${invocation.env.OPENAI_API_KEY ?? DEFAULT_API_KEY}`
|
|
146
|
+
},
|
|
147
|
+
method: "GET"
|
|
148
|
+
});
|
|
149
|
+
} catch (error) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`Could not reach Hoopilot at ${modelsUrl}. Start Hoopilot first, or set CODEXX_SKIP_MODEL_PREFLIGHT=1 to skip this check. ${errorMessage(error)}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`Could not verify model ${JSON.stringify(invocation.model)} because ${modelsUrl} returned ${response.status}: ${await shortResponseText(response)}`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
const models = modelIds(await response.json().catch(() => void 0));
|
|
160
|
+
if (models.length > 0 && !models.includes(invocation.model)) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`The logged-in Copilot account does not advertise model ${JSON.stringify(invocation.model)} at ${modelsUrl}. Available models: ${models.join(", ")}. After upgrading Hoopilot, rerun "hoopilot login" to refresh the Copilot OAuth token, or set CODEXX_MODEL to one of the advertised model IDs.`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function helpText() {
|
|
167
|
+
return `codexx
|
|
168
|
+
|
|
169
|
+
Run Codex against an already-running local Hoopilot server.
|
|
170
|
+
|
|
171
|
+
Usage:
|
|
172
|
+
codexx [codex options] [prompt]
|
|
173
|
+
|
|
174
|
+
Environment:
|
|
175
|
+
CODEXX_BASE_URL OpenAI-compatible base URL. Default: ${DEFAULT_BASE_URL}
|
|
176
|
+
CODEXX_API_KEY API key sent to the local Hoopilot server.
|
|
177
|
+
HOOPILOT_API_KEY Used as the API key when CODEXX_API_KEY is unset.
|
|
178
|
+
CODEXX_CODEX_BIN Codex executable to run. Default: ${DEFAULT_CODEX_BIN}
|
|
179
|
+
CODEXX_MODEL Codex model to use. Default: ${DEFAULT_MODEL}
|
|
180
|
+
CODEXX_MODEL_REASONING_EFFORT
|
|
181
|
+
Codex reasoning effort. Default: ${DEFAULT_REASONING_EFFORT}
|
|
182
|
+
CODEXX_SKIP_MODEL_PREFLIGHT
|
|
183
|
+
Set to 1 to skip checking /v1/models before starting Codex.
|
|
184
|
+
|
|
185
|
+
codexx does not start Hoopilot and does not change your shell environment. It selects a temporary Hoopilot model provider with Responses WebSockets disabled, uses ${DEFAULT_MODEL} with ${DEFAULT_REASONING_EFFORT} reasoning by default, disables Codex's network_proxy feature, and removes proxy variables only from the spawned Codex process.`;
|
|
186
|
+
}
|
|
187
|
+
function signalNumber(signal) {
|
|
188
|
+
return osConstants.signals[signal] ?? 1;
|
|
189
|
+
}
|
|
190
|
+
function modelIds(value) {
|
|
191
|
+
const record = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
192
|
+
const data = "data" in record && Array.isArray(record.data) ? record.data : [];
|
|
193
|
+
return data.map(
|
|
194
|
+
(entry) => entry && typeof entry === "object" && "id" in entry && typeof entry.id === "string" ? entry.id : void 0
|
|
195
|
+
).filter((id) => typeof id === "string" && id.length > 0);
|
|
196
|
+
}
|
|
197
|
+
async function shortResponseText(response) {
|
|
198
|
+
const text = await response.text();
|
|
199
|
+
return text.slice(0, 500);
|
|
200
|
+
}
|
|
201
|
+
function errorMessage(error) {
|
|
202
|
+
return error instanceof Error ? error.message : String(error);
|
|
203
|
+
}
|
|
204
|
+
if (import.meta.main) {
|
|
205
|
+
main().catch((error) => {
|
|
206
|
+
console.error(errorMessage(error));
|
|
207
|
+
process.exit(1);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export {
|
|
212
|
+
trimTrailingSlash,
|
|
213
|
+
envValue,
|
|
214
|
+
isTrustedTokenBaseUrl,
|
|
215
|
+
truncatedResponseText,
|
|
216
|
+
asRecord,
|
|
217
|
+
buildCodexxInvocation,
|
|
218
|
+
main,
|
|
219
|
+
verifyCodexxModel
|
|
220
|
+
};
|
|
221
|
+
//# sourceMappingURL=chunk-7GSQVYYT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/codexx.ts","../src/util.ts"],"sourcesContent":["#!/usr/bin/env bun\n\nimport { spawn } from \"node:child_process\";\nimport { constants as osConstants } from \"node:os\";\nimport type { FetchLike } from \"./types\";\nimport { envValue } from \"./util\";\n\nconst DEFAULT_BASE_URL = \"http://127.0.0.1:4141/v1\";\nconst DEFAULT_API_KEY = \"local-key\";\nconst DEFAULT_CODEX_BIN = \"codex\";\nconst DEFAULT_MODEL = \"gpt-5.5\";\nconst DEFAULT_REASONING_EFFORT = \"xhigh\";\nconst PROXY_ENV_KEYS = [\n \"ALL_PROXY\",\n \"HTTPS_PROXY\",\n \"HTTP_PROXY\",\n \"NO_PROXY\",\n \"all_proxy\",\n \"https_proxy\",\n \"http_proxy\",\n \"no_proxy\",\n];\n\nexport interface CodexxInvocation {\n args: string[];\n baseUrl: string;\n command: string;\n env: NodeJS.ProcessEnv;\n model: string;\n}\n\nexport function buildCodexxInvocation(\n argv: string[],\n env: NodeJS.ProcessEnv = process.env,\n): CodexxInvocation {\n const baseUrl = envValue(env.CODEXX_BASE_URL) ?? DEFAULT_BASE_URL;\n const apiKey = envValue(env.CODEXX_API_KEY) ?? envValue(env.HOOPILOT_API_KEY) ?? DEFAULT_API_KEY;\n const command = envValue(env.CODEXX_CODEX_BIN) ?? DEFAULT_CODEX_BIN;\n const model = envValue(env.CODEXX_MODEL) ?? DEFAULT_MODEL;\n const reasoningEffort = envValue(env.CODEXX_MODEL_REASONING_EFFORT) ?? DEFAULT_REASONING_EFFORT;\n const providerConfig = [\n '{ name = \"Hoopilot\"',\n `base_url = ${JSON.stringify(baseUrl)}`,\n 'env_key = \"OPENAI_API_KEY\"',\n 'wire_api = \"responses\"',\n \"supports_websockets = false }\",\n ].join(\", \");\n\n return {\n args: [\n \"--disable\",\n \"network_proxy\",\n \"-c\",\n 'model_provider=\"hoopilot\"',\n \"-c\",\n `model_providers.hoopilot=${providerConfig}`,\n \"-m\",\n model,\n \"-c\",\n `model_reasoning_effort=${JSON.stringify(reasoningEffort)}`,\n ...argv,\n ],\n baseUrl,\n command,\n env: withoutProxyEnv({\n ...env,\n OPENAI_API_KEY: apiKey,\n }),\n model,\n };\n}\n\nfunction withoutProxyEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const next = { ...env };\n for (const key of PROXY_ENV_KEYS) {\n delete next[key];\n }\n return next;\n}\n\nexport async function main(argv = Bun.argv.slice(2), env = process.env): Promise<void> {\n if (argv.length === 1 && (argv[0] === \"--help\" || argv[0] === \"-h\")) {\n console.log(helpText());\n return;\n }\n\n const invocation = buildCodexxInvocation(argv, env);\n if (env.CODEXX_SKIP_MODEL_PREFLIGHT !== \"1\") {\n await verifyCodexxModel(invocation);\n }\n const child = spawn(invocation.command, invocation.args, {\n env: invocation.env,\n shell: process.platform === \"win32\",\n stdio: \"inherit\",\n });\n\n const exitCode = await new Promise<number>((resolve, reject) => {\n child.once(\"error\", reject);\n child.once(\"exit\", (code, signal) => {\n if (typeof code === \"number\") {\n resolve(code);\n return;\n }\n resolve(signal ? 128 + signalNumber(signal) : 1);\n });\n });\n\n process.exitCode = exitCode;\n}\n\nexport async function verifyCodexxModel(\n invocation: Pick<CodexxInvocation, \"baseUrl\" | \"env\" | \"model\">,\n fetcher: FetchLike = fetch,\n): Promise<void> {\n const modelsUrl = `${invocation.baseUrl.replace(/\\/+$/, \"\")}/models`;\n let response: Response;\n try {\n response = await fetcher(modelsUrl, {\n headers: {\n accept: \"application/json\",\n authorization: `Bearer ${invocation.env.OPENAI_API_KEY ?? DEFAULT_API_KEY}`,\n },\n method: \"GET\",\n });\n } catch (error) {\n throw new Error(\n `Could not reach Hoopilot at ${modelsUrl}. Start Hoopilot first, or set CODEXX_SKIP_MODEL_PREFLIGHT=1 to skip this check. ${errorMessage(error)}`,\n );\n }\n\n if (!response.ok) {\n throw new Error(\n `Could not verify model ${JSON.stringify(invocation.model)} because ${modelsUrl} returned ${response.status}: ${await shortResponseText(response)}`,\n );\n }\n\n const models = modelIds(await response.json().catch(() => undefined));\n if (models.length > 0 && !models.includes(invocation.model)) {\n throw new Error(\n `The logged-in Copilot account does not advertise model ${JSON.stringify(invocation.model)} at ${modelsUrl}. Available models: ${models.join(\", \")}. After upgrading Hoopilot, rerun \"hoopilot login\" to refresh the Copilot OAuth token, or set CODEXX_MODEL to one of the advertised model IDs.`,\n );\n }\n}\n\nfunction helpText(): string {\n return `codexx\n\nRun Codex against an already-running local Hoopilot server.\n\nUsage:\n codexx [codex options] [prompt]\n\nEnvironment:\n CODEXX_BASE_URL OpenAI-compatible base URL. Default: ${DEFAULT_BASE_URL}\n CODEXX_API_KEY API key sent to the local Hoopilot server.\n HOOPILOT_API_KEY Used as the API key when CODEXX_API_KEY is unset.\n CODEXX_CODEX_BIN Codex executable to run. Default: ${DEFAULT_CODEX_BIN}\n CODEXX_MODEL Codex model to use. Default: ${DEFAULT_MODEL}\n CODEXX_MODEL_REASONING_EFFORT\n Codex reasoning effort. Default: ${DEFAULT_REASONING_EFFORT}\n CODEXX_SKIP_MODEL_PREFLIGHT\n Set to 1 to skip checking /v1/models before starting Codex.\n\ncodexx does not start Hoopilot and does not change your shell environment. It selects a temporary Hoopilot model provider with Responses WebSockets disabled, uses ${DEFAULT_MODEL} with ${DEFAULT_REASONING_EFFORT} reasoning by default, disables Codex's network_proxy feature, and removes proxy variables only from the spawned Codex process.`;\n}\n\nfunction signalNumber(signal: NodeJS.Signals): number {\n return osConstants.signals[signal] ?? 1;\n}\n\nfunction modelIds(value: unknown): string[] {\n const record = value && typeof value === \"object\" && !Array.isArray(value) ? value : {};\n const data = \"data\" in record && Array.isArray(record.data) ? record.data : [];\n return data\n .map((entry) =>\n entry && typeof entry === \"object\" && \"id\" in entry && typeof entry.id === \"string\"\n ? entry.id\n : undefined,\n )\n .filter((id): id is string => typeof id === \"string\" && id.length > 0);\n}\n\nasync function shortResponseText(response: Response): Promise<string> {\n const text = await response.text();\n return text.slice(0, 500);\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nif (import.meta.main) {\n main().catch((error: unknown) => {\n console.error(errorMessage(error));\n process.exit(1);\n });\n}\n","import type { JsonObject } from \"./types\";\n\n/** Remove any trailing slashes from a URL or path string. */\nexport function trimTrailingSlash(value: string): string {\n return value.replace(/\\/+$/, \"\");\n}\n\n/** Treat blank environment variables as unset while preserving nonblank values. */\nexport function envValue(value: string | undefined): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n}\n\n/** True for HTTPS URLs, or HTTP only on loopback hosts used by local tests/dev. */\nexport function isHttpsOrLoopbackUrl(rawUrl: string): boolean {\n const url = parseUrl(rawUrl);\n if (!url) {\n return false;\n }\n return url.protocol === \"https:\" || isLoopbackHttpUrl(url);\n}\n\n/** Validate a base URL before sending a bearer/OAuth token to it. */\nexport function isTrustedTokenBaseUrl(\n rawUrl: string,\n allowedHttpsHosts: readonly string[],\n allowUnsafeHttps = false,\n): boolean {\n const url = parseUrl(rawUrl);\n if (!url) {\n return false;\n }\n if (url.username || url.password || url.search || url.hash) {\n return false;\n }\n if (url.pathname !== \"\" && url.pathname !== \"/\") {\n return false;\n }\n if (isLoopbackHttpUrl(url)) {\n return true;\n }\n if (url.protocol !== \"https:\") {\n return false;\n }\n const host = url.hostname.toLowerCase();\n return allowedHttpsHosts.includes(host) || allowUnsafeHttps;\n}\n\nfunction parseUrl(rawUrl: string): URL | undefined {\n let url: URL;\n try {\n url = new URL(rawUrl);\n } catch {\n return undefined;\n }\n return url;\n}\n\nfunction isLoopbackHttpUrl(url: URL): boolean {\n return (\n url.protocol === \"http:\" &&\n (url.hostname === \"127.0.0.1\" ||\n url.hostname === \"localhost\" ||\n url.hostname === \"::1\" ||\n url.hostname === \"[::1]\")\n );\n}\n\n/** Read a response body as text, truncated to keep error messages bounded. */\nexport async function truncatedResponseText(response: Response, max = 500): Promise<string> {\n const text = await response.text();\n return text.slice(0, max);\n}\n\n/** Narrow an unknown value to a plain object, returning {} for arrays/primitives/null. */\nexport function asRecord(value: unknown): JsonObject {\n return value && typeof value === \"object\" && !Array.isArray(value) ? (value as JsonObject) : {};\n}\n"],"mappings":"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","names":[]}
|