@openhoo/hoopilot 2.1.9 → 2.1.11

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.
@@ -6,6 +6,7 @@ import { constants as osConstants } from "os";
6
6
  var DEFAULT_MODEL = "gpt-5.5";
7
7
 
8
8
  // src/util.ts
9
+ import { isIP } from "net";
9
10
  function trimTrailingSlash(value) {
10
11
  return value.replace(/\/+$/, "");
11
12
  }
@@ -42,9 +43,16 @@ function parseUrl(rawUrl) {
42
43
  }
43
44
  return url;
44
45
  }
45
- var LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
46
46
  function isLoopbackHostname(host) {
47
- return LOOPBACK_HOSTNAMES.has(host);
47
+ const normalized = host.trim().toLowerCase();
48
+ const address = normalized.startsWith("[") && normalized.endsWith("]") ? normalized.slice(1, -1) : normalized;
49
+ if (address === "localhost") {
50
+ return true;
51
+ }
52
+ if (isIP(address) === 4) {
53
+ return address.startsWith("127.");
54
+ }
55
+ return isIP(address) === 6 && (address === "::1" || address === "0:0:0:0:0:0:0:1");
48
56
  }
49
57
  function isLoopbackHttpUrl(url) {
50
58
  return url.protocol === "http:" && isLoopbackHostname(url.hostname);
@@ -346,4 +354,4 @@ export {
346
354
  main,
347
355
  verifyCodexxModel
348
356
  };
349
- //# sourceMappingURL=chunk-FH6WSFOC.js.map
357
+ //# sourceMappingURL=chunk-2GIR4W4A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/codexx.ts","../src/defaults.ts","../src/util.ts"],"sourcesContent":["#!/usr/bin/env bun\n\nimport { spawn } from \"node:child_process\";\nimport { constants as osConstants } from \"node:os\";\nimport { DEFAULT_MODEL } from \"./defaults\";\nimport type { FetchLike } from \"./types\";\nimport {\n envValue,\n errorMessage,\n modelIdsFromResponse,\n trimTrailingSlash,\n truncatedResponseText,\n} from \"./util\";\n\nconst DEFAULT_BASE_URL = \"http://127.0.0.1:4141/v1\";\nconst DEFAULT_CODEX_BIN = \"codex\";\nconst DEFAULT_REASONING_EFFORT = \"xhigh\";\nconst DEFAULT_STREAM_IDLE_TIMEOUT_MS = 120_000;\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 // Never fall back to a public, predictable key: a shared constant like the old\n // \"local-key\" default is also a credential a malicious local/browser client\n // could guess. When no key is configured the local server is expected to run\n // unauthenticated, which accepts any value, so a random throwaway key is safe.\n const apiKey =\n envValue(env.CODEXX_API_KEY) ?? envValue(env.HOOPILOT_API_KEY) ?? generateEphemeralApiKey();\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 streamIdleTimeoutMs = parseStreamIdleTimeoutMs(env.CODEXX_STREAM_IDLE_TIMEOUT_MS);\n const providerConfigParts = [\n '{ name = \"Hoopilot\"',\n `base_url = ${JSON.stringify(baseUrl)}`,\n 'env_key = \"OPENAI_API_KEY\"',\n 'wire_api = \"responses\"',\n \"supports_websockets = false\",\n ];\n if (streamIdleTimeoutMs > 0) {\n providerConfigParts.push(`stream_idle_timeout_ms = ${streamIdleTimeoutMs}`);\n }\n const providerConfig = `${providerConfigParts.join(\", \")} }`;\n\n return {\n args: [\n // Codex ships a managed network proxy (codex-rs/network-proxy) that routes the\n // agent's traffic through a local proxy on :3128 and enforces a domain allowlist.\n // A host that is not allowlisted — like the local Hoopilot server — gets an instant\n // 403 (the Squid error page) and never reaches Hoopilot. It has two independent\n // gates: the `network_proxy` feature flag and the `permissions.workspace.network`\n // config. Disabling only the feature (`--disable network_proxy`) does not reliably\n // turn it off when the proxy is enabled through the permissions config, so set both\n // off: the feature flag via `--disable`, and the proxy itself via the config key\n // (when `enabled` is false the proxy no-ops and binds no listeners).\n \"--disable\",\n \"network_proxy\",\n \"-c\",\n \"permissions.workspace.network.enabled=false\",\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\n// A random, non-guessable placeholder key for when neither CODEXX_API_KEY nor\n// HOOPILOT_API_KEY is set. An unauthenticated local Hoopilot accepts any value;\n// a keyed server rejects it with a 401, which the model preflight surfaces.\nfunction generateEphemeralApiKey(): string {\n return `codexx-${crypto.randomUUID()}`;\n}\n\nfunction parseStreamIdleTimeoutMs(rawValue: string | undefined): number {\n const raw = envValue(rawValue);\n if (raw === undefined) {\n return DEFAULT_STREAM_IDLE_TIMEOUT_MS;\n }\n const value = Number(raw);\n if (!Number.isInteger(value) || value < 0) {\n throw new Error(\"CODEXX_STREAM_IDLE_TIMEOUT_MS must be a non-negative integer.\");\n }\n return value;\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 = `${trimTrailingSlash(invocation.baseUrl)}/models`;\n const apiKey = invocation.env.OPENAI_API_KEY;\n if (apiKey === undefined) {\n throw new Error(\n \"verifyCodexxModel requires invocation.env.OPENAI_API_KEY; build the invocation with buildCodexxInvocation.\",\n );\n }\n let response: Response;\n try {\n response = await fetcher(modelsUrl, {\n headers: {\n accept: \"application/json\",\n authorization: `Bearer ${apiKey}`,\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 truncatedResponseText(response)}`,\n );\n }\n\n const models = modelIdsFromResponse(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. When\n neither is set, a random throwaway key is generated for\n an unauthenticated local server.\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_STREAM_IDLE_TIMEOUT_MS\n Codex Responses stream idle timeout in milliseconds. Default:\n ${DEFAULT_STREAM_IDLE_TIMEOUT_MS}; set 0 to use Codex's own default.\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 managed network proxy (permissions.workspace.network.enabled=false) so requests reach the local server instead of being blocked by its allowlist, 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\nif (import.meta.main) {\n main().catch((error: unknown) => {\n console.error(errorMessage(error));\n process.exit(1);\n });\n}\n","/** Default model Hoopilot uses when a client does not supply one. */\nexport const DEFAULT_MODEL = \"gpt-5.5\";\n","import { isIP } from \"node:net\";\nimport type { JsonObject, StreamingProxyMode, UsageAccountingMode } 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\n/** True for hostnames that always resolve to the local machine. */\nexport function isLoopbackHostname(host: string): boolean {\n const normalized = host.trim().toLowerCase();\n const address =\n normalized.startsWith(\"[\") && normalized.endsWith(\"]\") ? normalized.slice(1, -1) : normalized;\n if (address === \"localhost\") {\n return true;\n }\n if (isIP(address) === 4) {\n return address.startsWith(\"127.\");\n }\n return isIP(address) === 6 && (address === \"::1\" || address === \"0:0:0:0:0:0:0:1\");\n}\n\nfunction isLoopbackHttpUrl(url: URL): boolean {\n return url.protocol === \"http:\" && isLoopbackHostname(url.hostname);\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\n/** Extract a human-readable message from an unknown thrown value. */\nexport function errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** Return the first finite number among the candidates, else undefined. */\nexport function firstNumber(...values: unknown[]): number | undefined {\n for (const value of values) {\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return value;\n }\n }\n return undefined;\n}\n\n/** Generate a dash-free random identifier for synthesized response/message ids. */\nexport function randomId(): string {\n return crypto.randomUUID().replaceAll(\"-\", \"\");\n}\n\n/** Drop keys whose value is undefined so they are omitted from JSON output. */\nexport function removeUndefined<T extends object>(value: T): T {\n return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined)) as T;\n}\n\n/** Parse JSON, returning undefined instead of throwing on malformed input. */\nexport function safeJsonParse(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/** Parse JSON into a plain object, returning undefined on malformed or non-object input. */\nexport function parseJsonObject(text: string): JsonObject | undefined {\n try {\n return asRecord(JSON.parse(text));\n } catch {\n return undefined;\n }\n}\n\n/**\n * Extract de-duplicated model IDs from an OpenAI-style `/models` response (an\n * object carrying a `data` array, or a bare array of model objects).\n */\nexport function modelIdsFromResponse(body: unknown): string[] {\n const record = asRecord(body);\n const data = Array.isArray(record.data) ? record.data : Array.isArray(body) ? body : [];\n const seen = new Set<string>();\n const ids: string[] = [];\n for (const model of data) {\n const id = asRecord(model).id;\n if (typeof id !== \"string\" || id.length === 0 || seen.has(id)) {\n continue;\n }\n seen.add(id);\n ids.push(id);\n }\n return ids;\n}\n\n/** Canonical set of accepted streaming-proxy modes, kept in sync with {@link StreamingProxyMode}. */\nexport const STREAMING_PROXY_MODES = [\n \"auto\",\n \"buffer\",\n \"live\",\n] as const satisfies readonly StreamingProxyMode[];\n\n/** Canonical set of accepted token/accounting modes, kept in sync with {@link UsageAccountingMode}. */\nexport const USAGE_ACCOUNTING_MODES = [\n \"basic\",\n \"full\",\n \"off\",\n] as const satisfies readonly UsageAccountingMode[];\n\n/** Validate a stream-mode string against the allowed {@link StreamingProxyMode} values. */\nexport function parseStreamingProxyMode(value: string): StreamingProxyMode {\n if ((STREAMING_PROXY_MODES as readonly string[]).includes(value)) {\n return value as StreamingProxyMode;\n }\n throw new Error(`Invalid stream mode: ${value}. Expected ${STREAMING_PROXY_MODES.join(\", \")}.`);\n}\n\n/** Validate a usage-accounting string against the allowed {@link UsageAccountingMode} values. */\nexport function parseUsageAccountingMode(value: string): UsageAccountingMode {\n if ((USAGE_ACCOUNTING_MODES as readonly string[]).includes(value)) {\n return value as UsageAccountingMode;\n }\n throw new Error(\n `Invalid usage accounting mode: ${value}. Expected ${USAGE_ACCOUNTING_MODES.join(\", \")}.`,\n );\n}\n\n/** Parse common environment boolean spellings. */\nexport function parseBooleanEnv(value: string | undefined, name: string): boolean | undefined {\n const raw = envValue(value)?.toLowerCase();\n if (raw === undefined) {\n return undefined;\n }\n if (raw === \"1\" || raw === \"true\" || raw === \"yes\" || raw === \"on\") {\n return true;\n }\n if (raw === \"0\" || raw === \"false\" || raw === \"no\" || raw === \"off\") {\n return false;\n }\n throw new Error(`${name} must be one of: 1, 0, true, false, yes, no, on, off.`);\n}\n"],"mappings":"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","names":[]}
package/dist/cli.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  safeJsonParse,
19
19
  trimTrailingSlash,
20
20
  truncatedResponseText
21
- } from "./chunk-FH6WSFOC.js";
21
+ } from "./chunk-2GIR4W4A.js";
22
22
 
23
23
  // src/cli.ts
24
24
  import { spawn } from "child_process";
@@ -958,15 +958,10 @@ function responsesCompactionRequestBody(request) {
958
958
  return JSON.stringify(
959
959
  removeUndefined({
960
960
  ...request,
961
- input: [
962
- ...compactionInputItemsForCopilot(request.input),
963
- {
964
- content: [{ text: COMPACTION_SUMMARIZATION_PROMPT, type: "input_text" }],
965
- role: "user",
966
- type: "message"
967
- }
968
- ],
961
+ input: compactionInputItemsForCopilot(request.input),
962
+ instructions: compactionInstructionsForCopilot(request.instructions),
969
963
  parallel_tool_calls: false,
964
+ reasoning: void 0,
970
965
  stream: false,
971
966
  tool_choice: "none",
972
967
  tools: []
@@ -1297,6 +1292,9 @@ function compactionInputItemsForCopilot(input) {
1297
1292
  }
1298
1293
  ] : [];
1299
1294
  }
1295
+ function compactionInstructionsForCopilot(instructions) {
1296
+ return contentToText(instructions).trim() ? instructions : COMPACTION_SUMMARIZATION_PROMPT;
1297
+ }
1300
1298
  function responseInputItems(input) {
1301
1299
  return Array.isArray(input) ? input : [];
1302
1300
  }
@@ -1602,7 +1600,7 @@ function anthropicMessagesToResponsesInput(messages) {
1602
1600
  arguments: JSON.stringify(asRecord(part.input)),
1603
1601
  cache_control: anthropicCacheControl(part.cache_control),
1604
1602
  call_id: textValue(part.id) || `call_hoopilot_${fallbackToolCallIndex++}`,
1605
- name: textValue(part.name),
1603
+ name: requiredAnthropicText(part.name, "tool_use name"),
1606
1604
  type: "function_call"
1607
1605
  })
1608
1606
  );
@@ -1748,7 +1746,7 @@ function anthropicTools(tools) {
1748
1746
  return removeUndefined({
1749
1747
  cache_control: anthropicCacheControl(record.cache_control),
1750
1748
  description: record.description,
1751
- name: record.name,
1749
+ name: requiredAnthropicText(record.name, "tool name"),
1752
1750
  parameters: record.input_schema,
1753
1751
  strict: record.strict,
1754
1752
  type: "function"
@@ -1821,12 +1819,19 @@ function anthropicToolChoice(toolChoice) {
1821
1819
  return "none";
1822
1820
  }
1823
1821
  if (type === "tool") {
1824
- return { name: textValue(record.name), type: "function" };
1822
+ return { name: requiredAnthropicText(record.name, "tool_choice name"), type: "function" };
1825
1823
  }
1826
1824
  throw new AnthropicCompatibilityError(
1827
1825
  `Anthropic tool_choice type "${type || "unknown"}" is not supported.`
1828
1826
  );
1829
1827
  }
1828
+ function requiredAnthropicText(value, field) {
1829
+ const text = textValue(value).trim();
1830
+ if (!text) {
1831
+ throw new AnthropicCompatibilityError(`Anthropic ${field} is required.`);
1832
+ }
1833
+ return text;
1834
+ }
1830
1835
  function anthropicThinkingToReasoning(thinking) {
1831
1836
  const record = asRecord(thinking);
1832
1837
  if (Object.keys(record).length === 0) {
@@ -3115,6 +3120,17 @@ function isLoopbackOrigin(origin) {
3115
3120
  }
3116
3121
 
3117
3122
  // src/http/responses.ts
3123
+ var HOP_BY_HOP_HEADERS = [
3124
+ "connection",
3125
+ "keep-alive",
3126
+ "proxy-authenticate",
3127
+ "proxy-authorization",
3128
+ "te",
3129
+ "trailer",
3130
+ "transfer-encoding",
3131
+ "upgrade"
3132
+ ];
3133
+ var STALE_BODY_HEADERS = ["content-encoding", "content-length"];
3118
3134
  function jsonResponse(body, status = 200) {
3119
3135
  return new Response(JSON.stringify(body), {
3120
3136
  headers: {
@@ -3154,9 +3170,7 @@ function responseFromText(source, text) {
3154
3170
  }
3155
3171
  function proxyResponse(upstream) {
3156
3172
  const headers = new Headers(upstream.headers);
3157
- headers.delete("content-encoding");
3158
- headers.delete("content-length");
3159
- headers.delete("transfer-encoding");
3173
+ stripProxyUnsafeHeaders(headers);
3160
3174
  for (const [key, value] of Object.entries(corsHeaders())) {
3161
3175
  headers.set(key, value);
3162
3176
  }
@@ -3182,6 +3196,23 @@ function websocketUnsupportedResponse() {
3182
3196
  response.headers.set("upgrade", "websocket");
3183
3197
  return response;
3184
3198
  }
3199
+ function stripProxyUnsafeHeaders(headers) {
3200
+ const connection = headers.get("connection");
3201
+ if (connection) {
3202
+ for (const name of connection.split(",")) {
3203
+ const trimmed = name.trim();
3204
+ if (trimmed) {
3205
+ headers.delete(trimmed);
3206
+ }
3207
+ }
3208
+ }
3209
+ for (const name of HOP_BY_HOP_HEADERS) {
3210
+ headers.delete(name);
3211
+ }
3212
+ for (const name of STALE_BODY_HEADERS) {
3213
+ headers.delete(name);
3214
+ }
3215
+ }
3185
3216
 
3186
3217
  // src/metrics.ts
3187
3218
  var PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
@@ -4753,7 +4784,7 @@ function dashboardResponse() {
4753
4784
  }
4754
4785
  async function handleUsage(metrics, readUsage, request) {
4755
4786
  const view = new URL(request.url).searchParams.get("view");
4756
- const { copilot, error } = await readUsage(request.signal);
4787
+ const { copilot, error } = await readUsage();
4757
4788
  const proxy = view === DASHBOARD_USAGE_VIEW ? metrics.snapshot({
4758
4789
  excludeRoutes: DASHBOARD_EXCLUDED_ROUTES,
4759
4790
  excludeUpstreamPaths: DASHBOARD_EXCLUDED_UPSTREAM_PATHS