billion-context 0.1.30 → 0.1.31

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/dist/index.js CHANGED
@@ -1141,14 +1141,14 @@ var require_util = __commonJS({
1141
1141
  }
1142
1142
  const port = url.port != null ? url.port : url.protocol === "https:" ? 443 : 80;
1143
1143
  let origin = url.origin != null ? url.origin : `${url.protocol || ""}//${url.hostname || ""}:${port}`;
1144
- let path11 = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`;
1144
+ let path8 = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`;
1145
1145
  if (origin[origin.length - 1] === "/") {
1146
1146
  origin = origin.slice(0, origin.length - 1);
1147
1147
  }
1148
- if (path11 && path11[0] !== "/") {
1149
- path11 = `/${path11}`;
1148
+ if (path8 && path8[0] !== "/") {
1149
+ path8 = `/${path8}`;
1150
1150
  }
1151
- return new URL(`${origin}${path11}`);
1151
+ return new URL(`${origin}${path8}`);
1152
1152
  }
1153
1153
  if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
1154
1154
  throw new InvalidArgumentError("Invalid URL protocol: the URL must start with `http:` or `https:`.");
@@ -1969,9 +1969,9 @@ var require_diagnostics = __commonJS({
1969
1969
  "undici:client:sendHeaders",
1970
1970
  (evt) => {
1971
1971
  const {
1972
- request: { method, path: path11, origin }
1972
+ request: { method, path: path8, origin }
1973
1973
  } = evt;
1974
- debugLog("sending request to %s %s%s", method, origin, path11);
1974
+ debugLog("sending request to %s %s%s", method, origin, path8);
1975
1975
  }
1976
1976
  );
1977
1977
  }
@@ -1989,14 +1989,14 @@ var require_diagnostics = __commonJS({
1989
1989
  "undici:request:headers",
1990
1990
  (evt) => {
1991
1991
  const {
1992
- request: { method, path: path11, origin },
1992
+ request: { method, path: path8, origin },
1993
1993
  response: { statusCode }
1994
1994
  } = evt;
1995
1995
  debugLog(
1996
1996
  "received response to %s %s%s - HTTP %d",
1997
1997
  method,
1998
1998
  origin,
1999
- path11,
1999
+ path8,
2000
2000
  statusCode
2001
2001
  );
2002
2002
  }
@@ -2005,23 +2005,23 @@ var require_diagnostics = __commonJS({
2005
2005
  "undici:request:trailers",
2006
2006
  (evt) => {
2007
2007
  const {
2008
- request: { method, path: path11, origin }
2008
+ request: { method, path: path8, origin }
2009
2009
  } = evt;
2010
- debugLog("trailers received from %s %s%s", method, origin, path11);
2010
+ debugLog("trailers received from %s %s%s", method, origin, path8);
2011
2011
  }
2012
2012
  );
2013
2013
  diagnosticsChannel.subscribe(
2014
2014
  "undici:request:error",
2015
2015
  (evt) => {
2016
2016
  const {
2017
- request: { method, path: path11, origin },
2017
+ request: { method, path: path8, origin },
2018
2018
  error
2019
2019
  } = evt;
2020
2020
  debugLog(
2021
2021
  "request to %s %s%s errored - %s",
2022
2022
  method,
2023
2023
  origin,
2024
- path11,
2024
+ path8,
2025
2025
  error.message
2026
2026
  );
2027
2027
  }
@@ -2136,7 +2136,7 @@ var require_request = __commonJS({
2136
2136
  var kHandler = /* @__PURE__ */ Symbol("handler");
2137
2137
  var Request = class {
2138
2138
  constructor(origin, {
2139
- path: path11,
2139
+ path: path8,
2140
2140
  method,
2141
2141
  body,
2142
2142
  headers,
@@ -2153,11 +2153,11 @@ var require_request = __commonJS({
2153
2153
  maxRedirections,
2154
2154
  typeOfService
2155
2155
  }, handler) {
2156
- if (typeof path11 !== "string") {
2156
+ if (typeof path8 !== "string") {
2157
2157
  throw new InvalidArgumentError("path must be a string");
2158
- } else if (path11[0] !== "/" && !(path11.startsWith("http://") || path11.startsWith("https://")) && method !== "CONNECT") {
2158
+ } else if (path8[0] !== "/" && !(path8.startsWith("http://") || path8.startsWith("https://")) && method !== "CONNECT") {
2159
2159
  throw new InvalidArgumentError("path must be an absolute URL or start with a slash");
2160
- } else if (invalidPathRegex.test(path11)) {
2160
+ } else if (invalidPathRegex.test(path8)) {
2161
2161
  throw new InvalidArgumentError("invalid request path");
2162
2162
  }
2163
2163
  if (typeof method !== "string") {
@@ -2232,7 +2232,7 @@ var require_request = __commonJS({
2232
2232
  this.completed = false;
2233
2233
  this.aborted = false;
2234
2234
  this.upgrade = upgrade || null;
2235
- this.path = query ? serializePathWithQuery(path11, query) : path11;
2235
+ this.path = query ? serializePathWithQuery(path8, query) : path8;
2236
2236
  this.origin = origin;
2237
2237
  this.protocol = getProtocolFromUrlString(origin);
2238
2238
  this.idempotent = idempotent == null ? method === "HEAD" || method === "GET" : idempotent;
@@ -7415,7 +7415,7 @@ var require_client_h1 = __commonJS({
7415
7415
  return method !== "GET" && method !== "HEAD" && method !== "OPTIONS" && method !== "TRACE" && method !== "CONNECT";
7416
7416
  }
7417
7417
  function writeH1(client, request) {
7418
- const { method, path: path11, host, upgrade, blocking, reset } = request;
7418
+ const { method, path: path8, host, upgrade, blocking, reset } = request;
7419
7419
  let { body, headers, contentLength } = request;
7420
7420
  const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH" || method === "QUERY" || method === "PROPFIND" || method === "PROPPATCH";
7421
7421
  if (util.isFormDataLike(body)) {
@@ -7493,7 +7493,7 @@ var require_client_h1 = __commonJS({
7493
7493
  if (socket.setTypeOfService) {
7494
7494
  socket.setTypeOfService(request.typeOfService);
7495
7495
  }
7496
- let header = `${method} ${path11} HTTP/1.1\r
7496
+ let header = `${method} ${path8} HTTP/1.1\r
7497
7497
  `;
7498
7498
  if (typeof host === "string") {
7499
7499
  header += `host: ${host}\r
@@ -8146,7 +8146,7 @@ var require_client_h2 = __commonJS({
8146
8146
  function writeH2(client, request) {
8147
8147
  const requestTimeout = request.bodyTimeout ?? client[kBodyTimeout];
8148
8148
  const session = client[kHTTP2Session];
8149
- const { method, path: path11, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request;
8149
+ const { method, path: path8, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request;
8150
8150
  let { body } = request;
8151
8151
  if (upgrade != null && upgrade !== "websocket") {
8152
8152
  util.errorRequest(client, request, new InvalidArgumentError(`Custom upgrade "${upgrade}" not supported over HTTP/2`));
@@ -8214,7 +8214,7 @@ var require_client_h2 = __commonJS({
8214
8214
  }
8215
8215
  headers[HTTP2_HEADER_METHOD] = "CONNECT";
8216
8216
  headers[HTTP2_HEADER_PROTOCOL] = "websocket";
8217
- headers[HTTP2_HEADER_PATH] = path11;
8217
+ headers[HTTP2_HEADER_PATH] = path8;
8218
8218
  if (protocol === "ws:" || protocol === "wss:") {
8219
8219
  headers[HTTP2_HEADER_SCHEME] = protocol === "ws:" ? "http" : "https";
8220
8220
  } else {
@@ -8255,7 +8255,7 @@ var require_client_h2 = __commonJS({
8255
8255
  stream2.setTimeout(requestTimeout);
8256
8256
  return true;
8257
8257
  }
8258
- headers[HTTP2_HEADER_PATH] = path11;
8258
+ headers[HTTP2_HEADER_PATH] = path8;
8259
8259
  headers[HTTP2_HEADER_SCHEME] = protocol === "http:" ? "http" : "https";
8260
8260
  const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH";
8261
8261
  if (body && typeof body.read === "function") {
@@ -10598,10 +10598,10 @@ var require_proxy_agent = __commonJS({
10598
10598
  };
10599
10599
  const {
10600
10600
  origin,
10601
- path: path11 = "/",
10601
+ path: path8 = "/",
10602
10602
  headers = {}
10603
10603
  } = opts;
10604
- opts.path = origin + path11;
10604
+ opts.path = origin + path8;
10605
10605
  if (!("host" in headers) && !("Host" in headers)) {
10606
10606
  const { host } = new URL(origin);
10607
10607
  headers.host = host;
@@ -12684,20 +12684,20 @@ var require_mock_utils = __commonJS({
12684
12684
  }
12685
12685
  return normalizedQp;
12686
12686
  }
12687
- function safeUrl(path11) {
12688
- if (typeof path11 !== "string") {
12689
- return path11;
12687
+ function safeUrl(path8) {
12688
+ if (typeof path8 !== "string") {
12689
+ return path8;
12690
12690
  }
12691
- const pathSegments = path11.split("?", 3);
12691
+ const pathSegments = path8.split("?", 3);
12692
12692
  if (pathSegments.length !== 2) {
12693
- return path11;
12693
+ return path8;
12694
12694
  }
12695
12695
  const qp = new URLSearchParams(pathSegments.pop());
12696
12696
  qp.sort();
12697
12697
  return [...pathSegments, qp.toString()].join("?");
12698
12698
  }
12699
- function matchKey(mockDispatch2, { path: path11, method, body, headers }) {
12700
- const pathMatch = matchValue(mockDispatch2.path, path11);
12699
+ function matchKey(mockDispatch2, { path: path8, method, body, headers }) {
12700
+ const pathMatch = matchValue(mockDispatch2.path, path8);
12701
12701
  const methodMatch = matchValue(mockDispatch2.method, method);
12702
12702
  const bodyMatch = typeof mockDispatch2.body !== "undefined" ? matchValue(mockDispatch2.body, body) : true;
12703
12703
  const headersMatch = matchHeaders(mockDispatch2, headers);
@@ -12722,8 +12722,8 @@ var require_mock_utils = __commonJS({
12722
12722
  const basePath = key.query ? serializePathWithQuery(key.path, key.query) : key.path;
12723
12723
  const resolvedPath = typeof basePath === "string" ? safeUrl(basePath) : basePath;
12724
12724
  const resolvedPathWithoutTrailingSlash = removeTrailingSlash(resolvedPath);
12725
- let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path11, ignoreTrailingSlash }) => {
12726
- return ignoreTrailingSlash ? matchValue(removeTrailingSlash(safeUrl(path11)), resolvedPathWithoutTrailingSlash) : matchValue(safeUrl(path11), resolvedPath);
12725
+ let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path8, ignoreTrailingSlash }) => {
12726
+ return ignoreTrailingSlash ? matchValue(removeTrailingSlash(safeUrl(path8)), resolvedPathWithoutTrailingSlash) : matchValue(safeUrl(path8), resolvedPath);
12727
12727
  });
12728
12728
  if (matchedMockDispatches.length === 0) {
12729
12729
  throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`);
@@ -12762,19 +12762,19 @@ var require_mock_utils = __commonJS({
12762
12762
  mockDispatches.splice(index, 1);
12763
12763
  }
12764
12764
  }
12765
- function removeTrailingSlash(path11) {
12766
- while (path11.endsWith("/")) {
12767
- path11 = path11.slice(0, -1);
12765
+ function removeTrailingSlash(path8) {
12766
+ while (path8.endsWith("/")) {
12767
+ path8 = path8.slice(0, -1);
12768
12768
  }
12769
- if (path11.length === 0) {
12770
- path11 = "/";
12769
+ if (path8.length === 0) {
12770
+ path8 = "/";
12771
12771
  }
12772
- return path11;
12772
+ return path8;
12773
12773
  }
12774
12774
  function buildKey(opts) {
12775
- const { path: path11, method, body, headers, query } = opts;
12775
+ const { path: path8, method, body, headers, query } = opts;
12776
12776
  return {
12777
- path: path11,
12777
+ path: path8,
12778
12778
  method,
12779
12779
  body,
12780
12780
  headers,
@@ -13464,10 +13464,10 @@ var require_pending_interceptors_formatter = __commonJS({
13464
13464
  }
13465
13465
  format(pendingInterceptors) {
13466
13466
  const withPrettyHeaders = pendingInterceptors.map(
13467
- ({ method, path: path11, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
13467
+ ({ method, path: path8, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
13468
13468
  Method: method,
13469
13469
  Origin: origin,
13470
- Path: path11,
13470
+ Path: path8,
13471
13471
  "Status code": statusCode,
13472
13472
  Persistent: persist ? PERSISTENT : NOT_PERSISTENT,
13473
13473
  Invocations: timesInvoked,
@@ -13549,9 +13549,9 @@ var require_mock_agent = __commonJS({
13549
13549
  const acceptNonStandardSearchParameters = this[kMockAgentAcceptsNonStandardSearchParameters];
13550
13550
  const dispatchOpts = { ...opts };
13551
13551
  if (acceptNonStandardSearchParameters && dispatchOpts.path) {
13552
- const [path11, searchParams] = dispatchOpts.path.split("?");
13552
+ const [path8, searchParams] = dispatchOpts.path.split("?");
13553
13553
  const normalizedSearchParams = normalizeSearchParams(searchParams, acceptNonStandardSearchParameters);
13554
- dispatchOpts.path = `${path11}?${normalizedSearchParams}`;
13554
+ dispatchOpts.path = `${path8}?${normalizedSearchParams}`;
13555
13555
  }
13556
13556
  return this[kAgent].dispatch(dispatchOpts, handler);
13557
13557
  }
@@ -13952,12 +13952,12 @@ var require_snapshot_recorder = __commonJS({
13952
13952
  * @return {Promise<void>} - Resolves when snapshots are loaded
13953
13953
  */
13954
13954
  async loadSnapshots(filePath) {
13955
- const path11 = filePath || this.#snapshotPath;
13956
- if (!path11) {
13955
+ const path8 = filePath || this.#snapshotPath;
13956
+ if (!path8) {
13957
13957
  throw new InvalidArgumentError("Snapshot path is required");
13958
13958
  }
13959
13959
  try {
13960
- const data = await readFile3(resolve(path11), "utf8");
13960
+ const data = await readFile3(resolve(path8), "utf8");
13961
13961
  const parsed = JSON.parse(data);
13962
13962
  if (Array.isArray(parsed)) {
13963
13963
  this.#snapshots.clear();
@@ -13971,7 +13971,7 @@ var require_snapshot_recorder = __commonJS({
13971
13971
  if (error.code === "ENOENT") {
13972
13972
  this.#snapshots.clear();
13973
13973
  } else {
13974
- throw new UndiciError(`Failed to load snapshots from ${path11}`, { cause: error });
13974
+ throw new UndiciError(`Failed to load snapshots from ${path8}`, { cause: error });
13975
13975
  }
13976
13976
  }
13977
13977
  }
@@ -13982,11 +13982,11 @@ var require_snapshot_recorder = __commonJS({
13982
13982
  * @returns {Promise<void>} - Resolves when snapshots are saved
13983
13983
  */
13984
13984
  async saveSnapshots(filePath) {
13985
- const path11 = filePath || this.#snapshotPath;
13986
- if (!path11) {
13985
+ const path8 = filePath || this.#snapshotPath;
13986
+ if (!path8) {
13987
13987
  throw new InvalidArgumentError("Snapshot path is required");
13988
13988
  }
13989
- const resolvedPath = resolve(path11);
13989
+ const resolvedPath = resolve(path8);
13990
13990
  await mkdir3(dirname6(resolvedPath), { recursive: true });
13991
13991
  const data = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({
13992
13992
  hash,
@@ -14618,15 +14618,15 @@ var require_redirect_handler = __commonJS({
14618
14618
  return;
14619
14619
  }
14620
14620
  const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)));
14621
- const path11 = search ? `${pathname}${search}` : pathname;
14622
- const redirectUrlString = `${origin}${path11}`;
14621
+ const path8 = search ? `${pathname}${search}` : pathname;
14622
+ const redirectUrlString = `${origin}${path8}`;
14623
14623
  for (const historyUrl of this.history) {
14624
14624
  if (historyUrl.toString() === redirectUrlString) {
14625
14625
  throw new InvalidArgumentError(`Redirect loop detected. Cannot redirect to ${origin}. This typically happens when using a Client or Pool with cross-origin redirects. Use an Agent for cross-origin redirects.`);
14626
14626
  }
14627
14627
  }
14628
14628
  this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin);
14629
- this.opts.path = path11;
14629
+ this.opts.path = path8;
14630
14630
  this.opts.origin = origin;
14631
14631
  this.opts.query = null;
14632
14632
  }
@@ -16395,10 +16395,10 @@ var require_cache_handler = __commonJS({
16395
16395
  }
16396
16396
  return locationUrl.pathname + locationUrl.search;
16397
16397
  }
16398
- function deleteCachedUri(store, cacheKey, path11) {
16398
+ function deleteCachedUri(store, cacheKey, path8) {
16399
16399
  deleteCachedValue(store, {
16400
16400
  ...cacheKey,
16401
- path: path11
16401
+ path: path8
16402
16402
  });
16403
16403
  for (let i = 0; i < util.safeHTTPMethods.length; i++) {
16404
16404
  const method = util.safeHTTPMethods[i];
@@ -16406,7 +16406,7 @@ var require_cache_handler = __commonJS({
16406
16406
  deleteCachedValue(store, {
16407
16407
  ...cacheKey,
16408
16408
  method,
16409
- path: path11
16409
+ path: path8
16410
16410
  });
16411
16411
  }
16412
16412
  }
@@ -16417,9 +16417,9 @@ var require_cache_handler = __commonJS({
16417
16417
  }
16418
16418
  const values = Array.isArray(headerValue2) ? headerValue2 : [headerValue2];
16419
16419
  for (let i = 0; i < values.length; i++) {
16420
- const path11 = getSameOriginPath(cacheKey, values[i]);
16421
- if (path11 !== void 0) {
16422
- deleteCachedUri(store, cacheKey, path11);
16420
+ const path8 = getSameOriginPath(cacheKey, values[i]);
16421
+ if (path8 !== void 0) {
16422
+ deleteCachedUri(store, cacheKey, path8);
16423
16423
  }
16424
16424
  }
16425
16425
  }
@@ -21297,11 +21297,11 @@ var require_fetch = __commonJS({
21297
21297
  function dispatch({ body }) {
21298
21298
  const url = requestCurrentURL(request);
21299
21299
  const agent = fetchParams.controller.dispatcher;
21300
- const path11 = url.pathname + url.search;
21300
+ const path8 = url.pathname + url.search;
21301
21301
  const hasTrailingQuestionMark = url.search.length === 0 && url.href[url.href.length - url.hash.length - 1] === "?";
21302
21302
  return new Promise((resolve, reject) => agent.dispatch(
21303
21303
  {
21304
- path: hasTrailingQuestionMark ? `${path11}?` : path11,
21304
+ path: hasTrailingQuestionMark ? `${path8}?` : path8,
21305
21305
  origin: url.origin,
21306
21306
  method: request.method,
21307
21307
  body: agent.isMockActive ? request.body && (request.body.source || request.body.stream) : body,
@@ -22248,9 +22248,9 @@ var require_util4 = __commonJS({
22248
22248
  }
22249
22249
  }
22250
22250
  }
22251
- function validateCookiePath(path11) {
22252
- for (let i = 0; i < path11.length; ++i) {
22253
- const code = path11.charCodeAt(i);
22251
+ function validateCookiePath(path8) {
22252
+ for (let i = 0; i < path8.length; ++i) {
22253
+ const code = path8.charCodeAt(i);
22254
22254
  if (code < 32 || // exclude CTLs (0-31)
22255
22255
  code > 126 || // exclude DEL and non-ascii
22256
22256
  code === 59) {
@@ -25487,11 +25487,11 @@ var require_undici = __commonJS({
25487
25487
  if (typeof opts.path !== "string") {
25488
25488
  throw new InvalidArgumentError("invalid opts.path");
25489
25489
  }
25490
- let path11 = opts.path;
25490
+ let path8 = opts.path;
25491
25491
  if (!opts.path.startsWith("/")) {
25492
- path11 = `/${path11}`;
25492
+ path8 = `/${path8}`;
25493
25493
  }
25494
- url = new URL(util.parseOrigin(url).origin + path11);
25494
+ url = new URL(util.parseOrigin(url).origin + path8);
25495
25495
  } else {
25496
25496
  if (!opts) {
25497
25497
  opts = typeof url === "object" ? url : {};
@@ -30021,9 +30021,9 @@ var require_sha256 = __commonJS({
30021
30021
  var forge2 = require_forge();
30022
30022
  require_md();
30023
30023
  require_util7();
30024
- var sha2562 = module.exports = forge2.sha256 = forge2.sha256 || {};
30025
- forge2.md.sha256 = forge2.md.algorithms.sha256 = sha2562;
30026
- sha2562.create = function() {
30024
+ var sha256 = module.exports = forge2.sha256 = forge2.sha256 || {};
30025
+ forge2.md.sha256 = forge2.md.algorithms.sha256 = sha256;
30026
+ sha256.create = function() {
30027
30027
  if (!_initialized) {
30028
30028
  _init();
30029
30029
  }
@@ -46205,13 +46205,13 @@ function getUpstreamConnectionStatus() {
46205
46205
  }
46206
46206
 
46207
46207
  // src/config.ts
46208
- function safeReadJson(path11) {
46208
+ function safeReadJson(path8) {
46209
46209
  try {
46210
- const raw = readFileSync(path11, "utf8").replace(/^\uFEFF/, "");
46210
+ const raw = readFileSync(path8, "utf8").replace(/^\uFEFF/, "");
46211
46211
  return JSON.parse(raw);
46212
46212
  } catch (e) {
46213
46213
  if (e.code !== "ENOENT") {
46214
- log("error", `[acp-config] failed to parse ${path11}: ${String(e)}`);
46214
+ log("error", `[acp-config] failed to parse ${path8}: ${String(e)}`);
46215
46215
  }
46216
46216
  return void 0;
46217
46217
  }
@@ -48098,564 +48098,66 @@ function rewriteJsonResponse(body, ctx) {
48098
48098
  }
48099
48099
 
48100
48100
  // src/web/index.ts
48101
- import { readFileSync as readFileSync6 } from "fs";
48101
+ import { readFileSync as readFileSync4 } from "fs";
48102
48102
  import { dirname as dirname5, join as join3 } from "path";
48103
48103
  import { fileURLToPath } from "url";
48104
48104
 
48105
48105
  // src/web/client.ts
48106
48106
  var WEB_CLIENT = `
48107
- const byId=(id)=>document.getElementById(id);let routeState="disabled";
48107
+ const byId=(id)=>document.getElementById(id);
48108
48108
  const escapeHtml=(value)=>String(value??"").replace(/[&<>"']/g,(char)=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[char]);
48109
48109
  function toast(message,error){const node=byId("toast");node.textContent=message;node.className="toast show"+(error?" err":"");setTimeout(()=>node.className="toast"+(error?" err":""),2600)}
48110
48110
  function busy(button,on,label){if(!button)return;if(on){button.dataset.label=button.textContent;button.textContent=label||"\u5904\u7406\u4E2D\u2026";button.disabled=true;return}if(button.dataset.label){button.textContent=button.dataset.label;delete button.dataset.label}button.disabled=false}
48111
48111
  async function json(url,options){const response=await fetch(url,options);const data=await response.json().catch(()=>({}));if(!response.ok)throw new Error(data.error||data.detail||("HTTP "+response.status));return data}
48112
- function showPage(name){document.querySelectorAll(".page").forEach((node)=>node.classList.toggle("active",node.id==="page-"+name));document.querySelectorAll(".nav button").forEach((node)=>node.classList.toggle("active",node.dataset.page===name));if(name==="sessions")loadSessions();if(name==="routing")loadRouting();if(name==="upstream"){loadUpstream();loadOverrides()}}
48112
+ function showPage(name){document.querySelectorAll(".page").forEach((node)=>node.classList.toggle("active",node.id==="page-"+name));document.querySelectorAll(".nav button").forEach((node)=>node.classList.toggle("active",node.dataset.page===name));if(name==="sessions")loadSessions();if(name==="upstream"){loadUpstream();loadOverrides()}}
48113
48113
  document.querySelectorAll(".nav button").forEach((button)=>button.addEventListener("click",()=>showPage(button.dataset.page)));
48114
48114
  async function loadConfig(){const data=await json("/__bili/config");byId("providers-json").value=JSON.stringify(data.providers||{},null,2);byId("proxy-url").value=data.upstreamProxy||"";document.querySelectorAll('input[name="proxy-mode"]').forEach((input)=>input.checked=input.value===(data.upstreamProxyMode||"direct"));return data}
48115
- async function loadRouting(){try{const data=await json("/__bili/codex");routeState=data.state;const badge=byId("route-badge");const used=data.state==="enabled"&&data.lastRequestAt;badge.className="badge "+(data.state==="enabled"?(used?"ok":"warn"):data.state==="conflict"?"err":"");badge.textContent=data.state==="enabled"?(used?"\u4F7F\u7528\u4E2D":"\u7B49\u5F85\u9996\u6B21\u8BF7\u6C42"):data.state==="conflict"?"\u914D\u7F6E\u51B2\u7A81":"\u672A\u542F\u7528";byId("route-provider").textContent=data.providerId||(data.provider&&data.provider.id)||"\u2014";byId("route-real").textContent=data.originalBaseUrl||(data.provider&&data.provider.baseUrl)||"\u2014";byId("route-local").textContent=data.baseUrl||"\u2014";byId("route-detail").textContent=data.detail||(data.state==="enabled"&&!used?"\u82E5\u5F53\u524D Codex \u5C1A\u672A\u63A5\u5165\uFF0C\u53EF\u91CD\u65B0\u6253\u5F00 Codex\u3002":"");const button=byId("route-toggle");button.textContent=data.state==="disabled"?"\u542F\u7528 Codex \u8DEF\u7531":"\u6062\u590D\u539F\u914D\u7F6E";button.className="btn "+(data.state==="disabled"?"primary":"danger");button.disabled=false}catch(error){byId("route-detail").textContent=String(error)}}
48116
- async function toggleRoute(){const button=byId("route-toggle");const action=routeState==="disabled"?"enable":"disable";busy(button,true,action==="enable"?"\u542F\u7528\u4E2D\u2026":"\u6062\u590D\u4E2D\u2026");try{const data=await json("/__bili/codex/"+action,{method:"POST"});busy(button,false);await loadRouting();toast(data.detail||(action==="enable"?"Codex \u8DEF\u7531\u5DF2\u542F\u7528":"\u539F\u914D\u7F6E\u5DF2\u6062\u590D"))}catch(error){toast(String(error),true)}finally{busy(button,false)}}
48117
- async function copyRoute(){const button=byId("copy-route");busy(button,true,"\u590D\u5236\u4E2D\u2026");try{await navigator.clipboard.writeText(byId("route-local").textContent);toast("\u8DEF\u7531\u5730\u5740\u5DF2\u590D\u5236")}catch(error){toast(String(error),true)}finally{busy(button,false)}}
48118
48115
  async function loadUpstream(){try{const data=await json("/__bili/upstream");const labels={"provider":"Provider \u5355\u72EC\u4EE3\u7406","provider-direct":"Provider \u76F4\u8FDE","bili-env":"BILI_UPSTREAM_PROXY","web-manual":"Web \u624B\u52A8\u4EE3\u7406","config":"\u914D\u7F6E\u6587\u4EF6\u4EE3\u7406","HTTPS_PROXY":"HTTPS_PROXY","HTTP_PROXY":"HTTP_PROXY","ALL_PROXY":"ALL_PROXY","windows-system":"Windows \u7CFB\u7EDF\u4EE3\u7406","windows-bypass":"Windows \u7ED5\u8FC7\u5217\u8868","no-proxy":"NO_PROXY","direct":"\u76F4\u8FDE"};byId("upstream-source").textContent=labels[data.source]||data.source||"\u76F4\u8FDE";byId("upstream-effective").textContent=data.proxy||"direct";byId("upstream-pac").textContent=data.autoConfigUrl||"\u2014";const state=byId("upstream-state");state.className="status "+(data.connected===true?"ok":data.connected===false?"err":"");state.textContent=data.connected===true?"CONNECT \u6B63\u5E38":data.connected===false?(data.error||"\u8FDE\u63A5\u5931\u8D25"):"\u5C1A\u672A\u89C2\u5BDF\u5230\u4E0A\u6E38\u8FDE\u63A5"}catch(error){byId("upstream-state").textContent=String(error)}}
48119
48116
  async function saveUpstream(){const button=byId("save-upstream");busy(button,true,"\u4FDD\u5B58\u4E2D\u2026");const mode=document.querySelector('input[name="proxy-mode"]:checked').value;try{await json("/__bili/config",{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({upstreamProxyMode:mode,upstreamProxy:byId("proxy-url").value.trim()||null})});toast("\u4E0A\u6E38\u8BBE\u7F6E\u5DF2\u70ED\u66F4\u65B0");await loadUpstream()}catch(error){toast(String(error),true)}finally{busy(button,false)}}
48120
48117
  async function testUpstream(){const button=byId("test-upstream");busy(button,true,"\u6D4B\u8BD5\u4E2D\u2026");try{const data=await json("/__bili/upstream/test",{method:"POST"});toast("\u8FDE\u63A5\u6210\u529F\uFF0CHTTP "+data.status);await loadUpstream()}catch(error){toast(String(error),true);await loadUpstream()}finally{busy(button,false)}}
48121
- async function loadHistory(){try{const data=await json("/__bili/codex-history");byId("history-summary").textContent="\u5F53\u524D\u5206\u6876\uFF1A"+data.targetProviderId+"\\n\u68C0\u6D4B\u5230\u65E7\u7248 bili \u8DEF\u7531\u4F1A\u8BDD\uFF1A"+data.sessions+"\uFF08"+data.jsonlFiles+" \u4E2A JSONL\uFF0C"+data.stateRows+" \u6761 state DB\uFF09";byId("repair-history").disabled=data.sessions===0&&data.stateRows===0;if(data.stateDbError)byId("history-summary").textContent+="\\n"+data.stateDbError;return data}catch(error){byId("history-summary").textContent=String(error);throw error}}
48122
- async function previewHistory(){const button=byId("preview-history");busy(button,true,"\u9884\u89C8\u4E2D\u2026");try{await loadHistory();toast("\u5386\u53F2\u8BB0\u5F55\u9884\u89C8\u5DF2\u5237\u65B0")}catch(error){toast(String(error),true)}finally{busy(button,false)}}
48123
- async function repairHistory(){const button=byId("repair-history");if(!confirm("\u5C06\u5148\u5907\u4EFD\uFF0C\u518D\u628A\u786E\u8BA4\u5C5E\u4E8E\u65E7 bili \u8DEF\u7531\u7684\u4F1A\u8BDD\u8FC1\u79FB\u5230\u5F53\u524D provider\u3002\u7EE7\u7EED\uFF1F"))return;busy(button,true,"\u4FEE\u590D\u4E2D\u2026");let result;try{result=await json("/__bili/codex-history/repair",{method:"POST"});toast("\u5386\u53F2\u8BB0\u5F55\u5DF2\u4FEE\u590D\uFF0C\u5907\u4EFD\uFF1A"+result.backupPath)}catch(error){toast(String(error),true)}finally{busy(button,false)}if(result)await loadHistory().catch(()=>{})}
48124
48118
  async function saveProviders(){const button=byId("save-providers");busy(button,true,"\u4FDD\u5B58\u4E2D\u2026");try{const providers=JSON.parse(byId("providers-json").value);await json("/__bili/config",{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({providers})});await json("/__bili/config/reload",{method:"POST"});toast("Provider \u914D\u7F6E\u5DF2\u70ED\u66F4\u65B0")}catch(error){toast(String(error),true)}finally{busy(button,false)}}
48125
48119
  async function loadOverrides(){try{const data=await loadConfig();const providers=data.providers||{};const box=byId("upstream-overrides");const entries=Object.entries(providers);if(entries.length===0){box.innerHTML='<p class="status">\u6682\u65E0 Provider\u3002\u5728"\u9AD8\u7EA7\u8BBE\u7F6E"\u9875\u6DFB\u52A0 Provider \u540E\u53EF\u5728\u6B64\u6309 URL \u914D\u7F6E\u4EE3\u7406\u3002</p>';return}box.innerHTML='<table><thead><tr><th>Provider URL</th><th>\u4E0A\u6E38\u4EE3\u7406\uFF08\u7A7A=\u7EE7\u627F\u5168\u5C40\uFF09</th></tr></thead><tbody>'+entries.map(([url,route])=>'<tr><td class="mono">'+escapeHtml(url)+'</td><td><input class="mono override-proxy" data-url="'+escapeHtml(url)+'" value="'+escapeHtml((route&&route.proxy)||"")+'" placeholder="\u7EE7\u627F\u5168\u5C40"></td></tr>').join("")+'</tbody></table>'}catch(error){byId("upstream-overrides").textContent=String(error)}}
48126
48120
  async function saveOverrides(){const button=byId("save-overrides");busy(button,true,"\u4FDD\u5B58\u4E2D\u2026");try{const data=await loadConfig();const providers=data.providers||{};document.querySelectorAll(".override-proxy").forEach((input)=>{const url=input.dataset.url;if(!url)return;if(!providers[url])providers[url]={};const val=input.value.trim();if(val)providers[url].proxy=val;else delete providers[url].proxy});await json("/__bili/config",{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({providers})});await json("/__bili/config/reload",{method:"POST"});toast("\u6309 URL \u8986\u76D6\u5DF2\u70ED\u66F4\u65B0");await loadOverrides()}catch(error){toast(String(error),true)}finally{busy(button,false)}}
48127
48121
  async function loadSessions(){try{const data=await json("/__bili/stats");const rows=(data.sessions||[]).map((item)=>"<tr><td>"+escapeHtml(item.title||"\u2014")+"</td><td>"+escapeHtml(item.protocol||"\u2014")+"</td><td class=mono>"+escapeHtml(item.label||"\u2014")+"</td><td>"+escapeHtml(item.requests)+"</td><td>"+escapeHtml(item.contextTokens)+"</td><td>"+escapeHtml(new Date(item.lastSeen).toLocaleString())+"</td></tr>").join("");byId("sessions-body").innerHTML=rows||'<tr><td colspan="6">\u6682\u65E0\u4F1A\u8BDD</td></tr>'}catch(error){toast(String(error),true);throw error}}
48128
48122
  async function refreshSessions(){const button=byId("refresh-sessions");busy(button,true,"\u5237\u65B0\u4E2D\u2026");try{await loadSessions();toast("\u4F1A\u8BDD\u5217\u8868\u5DF2\u5237\u65B0")}catch{}finally{busy(button,false)}}
48129
- byId("route-toggle").addEventListener("click",toggleRoute);byId("copy-route").addEventListener("click",copyRoute);document.querySelectorAll(".copy-btn").forEach((btn)=>btn.addEventListener("click",async()=>{busy(btn,true,"\u590D\u5236\u4E2D\u2026");try{await navigator.clipboard.writeText(btn.dataset.copy||"");toast("\u5DF2\u590D\u5236")}catch(e){toast(String(e),true)}finally{busy(btn,false)}}));byId("save-upstream").addEventListener("click",saveUpstream);byId("test-upstream").addEventListener("click",testUpstream);byId("preview-history").addEventListener("click",previewHistory);byId("repair-history").addEventListener("click",repairHistory);byId("save-providers").addEventListener("click",saveProviders);byId("save-overrides").addEventListener("click",saveOverrides);byId("refresh-sessions").addEventListener("click",refreshSessions);
48130
- Promise.all([loadConfig(),loadRouting(),loadUpstream(),loadHistory()]).catch((error)=>toast(String(error),true));setInterval(()=>{if(byId("page-sessions").classList.contains("active"))loadSessions().catch(()=>{})},5000);
48123
+ document.querySelectorAll(".copy-btn").forEach((btn)=>btn.addEventListener("click",async()=>{busy(btn,true,"\u590D\u5236\u4E2D\u2026");try{await navigator.clipboard.writeText(btn.dataset.copy||"");toast("\u5DF2\u590D\u5236")}catch(e){toast(String(e),true)}finally{busy(btn,false)}}));byId("save-upstream").addEventListener("click",saveUpstream);byId("test-upstream").addEventListener("click",testUpstream);byId("save-providers").addEventListener("click",saveProviders);byId("save-overrides").addEventListener("click",saveOverrides);byId("refresh-sessions").addEventListener("click",refreshSessions);
48124
+ Promise.all([loadConfig(),loadUpstream()]).catch((error)=>toast(String(error),true));setInterval(()=>{if(byId("page-sessions").classList.contains("active"))loadSessions().catch(()=>{})},5000);
48131
48125
  `;
48132
48126
 
48133
48127
  // src/web/styles.ts
48134
48128
  var WEB_STYLES = `
48135
48129
  :root{--bg:#0f1115;--panel:#171a21;--panel2:#1e222c;--line:#2b313d;--text:#e7eaf0;--muted:#8f98a8;--blue:#6ea8fe;--green:#55c98b;--yellow:#e9b949;--red:#ef6b73;--radius:10px}
48136
48130
  *{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;min-height:100vh}.app{display:grid;grid-template-columns:220px 1fr;min-height:100vh}.sidebar{border-right:1px solid var(--line);padding:22px 14px;background:#12151b;position:sticky;top:0;height:100vh}.brand{font-size:18px;font-weight:700;padding:0 10px 22px}.brand span{color:var(--blue)}.version{display:block;color:var(--muted);font-size:11px;font-weight:400;margin-top:2px}.nav{display:grid;gap:4px}.nav button{appearance:none;border:0;background:transparent;color:var(--muted);text-align:left;padding:10px 12px;border-radius:7px;cursor:pointer;font:inherit}.nav button:hover,.nav button.active{background:var(--panel2);color:var(--text)}.main{padding:32px;max-width:1050px;width:100%}.page{display:none}.page.active{display:block}h1{font-size:23px;margin:0 0 4px}h2{font-size:15px;margin:0}.lead{color:var(--muted);margin:0 0 24px}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:18px;margin-bottom:14px}.card-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.badge{border-radius:99px;padding:3px 9px;font-size:12px;background:var(--panel2);color:var(--muted)}.badge.ok{color:var(--green)}.badge.warn{color:var(--yellow)}.badge.err{color:var(--red)}.kv{display:grid;grid-template-columns:135px 1fr;gap:10px 14px;margin:0}.kv dt{color:var(--muted)}.kv dd{margin:0;word-break:break-all}.mono{font-family:"Cascadia Code",Consolas,monospace;font-size:12px}.actions{display:flex;gap:9px;flex-wrap:wrap;margin-top:18px}.btn{border:1px solid var(--line);background:var(--panel2);color:var(--text);border-radius:7px;padding:8px 13px;cursor:pointer;font:inherit}.btn:hover{border-color:#4b5668}.btn.primary{background:var(--blue);border-color:var(--blue);color:#0b1220;font-weight:600}.btn.danger{color:var(--red)}.btn:disabled{opacity:.55;cursor:wait}.field{display:grid;gap:6px;margin:12px 0}.field label{color:var(--muted);font-size:12px}input,textarea,select{width:100%;background:#101319;color:var(--text);border:1px solid var(--line);border-radius:7px;padding:9px 10px;font:inherit}textarea{min-height:250px;font-family:"Cascadia Code",Consolas,monospace;font-size:12px;resize:vertical}.modes{display:flex;gap:18px;margin:12px 0}.modes label{display:flex;gap:7px;align-items:center;color:var(--muted)}.modes input{width:auto}.notice{border-left:3px solid var(--yellow);padding:10px 12px;background:rgba(233,185,73,.08);color:#d8c185;margin-top:14px}.status{color:var(--muted);white-space:pre-wrap}.status.ok{color:var(--green)}.status.err{color:var(--red)}table{width:100%;border-collapse:collapse}th,td{text-align:left;border-bottom:1px solid var(--line);padding:9px 8px;font-size:12px}th{color:var(--muted);font-weight:500}.toast{position:fixed;right:24px;bottom:24px;background:var(--green);color:#07140d;padding:10px 15px;border-radius:8px;opacity:0;transform:translateY(8px);transition:.2s;pointer-events:none}.toast.show{opacity:1;transform:none}.toast.err{background:var(--red);color:#1b0608}
48137
- .section-title{font-size:17px;margin:28px 0 4px}.hint{color:var(--muted);margin:0 0 14px;font-size:13px}.btn.small{padding:4px 10px;font-size:12px}
48131
+ .section-title{font-size:17px;margin:28px 0 4px}.hint{color:var(--muted);margin:0 0 14px;font-size:13px}.btn.small{padding:4px 10px;font-size:12px}.config-block{background:var(--panel2);border:1px solid var(--line);border-radius:6px;padding:10px 14px;margin:8px 0 12px;overflow-x:auto;font-size:13px}.config-block code{font-family:"Cascadia Code",Consolas,monospace;color:var(--text)}.fork-ribbon{position:fixed;top:0;right:0;background:#1a1a2e;color:#fff;padding:6px 16px;font-size:12px;text-decoration:none;border-radius:0 0 0 8px;z-index:100;opacity:.8;transition:.2s}.fork-ribbon:hover{opacity:1}
48138
48132
  @media(max-width:760px){.app{grid-template-columns:1fr}.sidebar{height:auto;position:static;border-right:0;border-bottom:1px solid var(--line)}.nav{display:flex;overflow:auto}.main{padding:20px}.grid{grid-template-columns:1fr}.kv{grid-template-columns:110px 1fr}}
48139
48133
  `;
48140
48134
 
48141
48135
  // src/web/page.ts
48142
48136
  function renderPage(origin, version2) {
48143
48137
  return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>billion-context</title><style>${WEB_STYLES}</style></head><body>
48144
- <div class="app"><aside class="sidebar"><div class="brand">billion<span>-context</span><span class="version">v${version2} \xB7 ${origin}</span></div><nav class="nav"><button class="active" data-page="overview">\u6982\u89C8</button><button data-page="routing">\u63A5\u5165</button><button data-page="upstream">\u4E0A\u6E38</button><button data-page="sessions">\u4F1A\u8BDD</button><button data-page="settings">\u8BBE\u7F6E</button></nav></aside>
48138
+ <div class="app"><aside class="sidebar"><div class="brand"><div class="brand-title">billion<span>-context</span></div><div class="version">v${version2}</div></div><a class="fork-ribbon" href="https://github.com/ranxianglei/billion-context" target="_blank" rel="noopener">\u2605 Fork me on GitHub</a><nav class="nav"><button class="active" data-page="overview">\u6982\u89C8</button><button data-page="routing">\u63A5\u5165</button><button data-page="upstream">\u4E0A\u6E38</button><button data-page="sessions">\u4F1A\u8BDD</button><button data-page="settings">\u8BBE\u7F6E</button></nav></aside>
48145
48139
  <main class="main"><section id="page-overview" class="page active"><h1>\u6982\u89C8</h1><p class="lead">\u672C\u5730\u8DEF\u7531\u4E0E\u4E0A\u6E38\u7F51\u7EDC\u76F8\u4E92\u72EC\u7ACB\uFF0CACP \u538B\u7F29\u8FD0\u884C\u5728\u4E24\u8005\u4E4B\u95F4\u3002</p><div class="grid"><div class="card"><h2>\u670D\u52A1\u5730\u5740</h2><p class="mono">${origin}</p></div><div class="card"><h2>\u5BA2\u6237\u7AEF\u63A5\u5165</h2><p class="status">\u5728"\u63A5\u5165"\u9875\u5C06\u5404\u5BA2\u6237\u7AEF\uFF08Codex / Claude Code / OpenCode / Pi \u7B49\uFF09\u7684\u6D41\u91CF\u8DEF\u7531\u5230 bili\u3002</p></div></div></section>
48146
- <section id="page-routing" class="page"><h1>\u5BA2\u6237\u7AEF\u63A5\u5165</h1><p class="lead">\u4E24\u7C7B\u63A5\u5165\uFF1AA. API-key \u5BA2\u6237\u7AEF\u52A0 <span class="mono">/bili/</span> \u524D\u7F00\uFF08\u96F6\u914D\u7F6E\uFF09\uFF1BB. \u8BA2\u9605/\u767B\u5F55\u5BA2\u6237\u7AEF\u7528 MITM \u6216\u8DEF\u7531\u63A5\u7BA1\u3002\u540C\u4E00\u5BA2\u6237\u7AEF\u4E24\u79CD\u65B9\u5F0F\u90FD\u53EF\u80FD\u7528\u5230\u3002</p>
48147
- <h2 class="section-title">A. API-key \u5BA2\u6237\u7AEF\uFF08/bili/ \u524D\u7F00\uFF09</h2>
48148
- <p class="hint">\u5728\u5BA2\u6237\u7AEF\u914D\u7F6E\u91CC\u628A\u539F baseURL \u524D\u52A0\u4E0A <span class="mono">${origin}/bili/</span> \u5373\u53EF\uFF0C\u5176\u4F59\u4E0D\u53D8\u3002\u4E0A\u4E0B\u6587\u7A97\u53E3\u9ED8\u8BA4\u6309\u6A21\u578B\u540D\u5339\u914D\u5185\u7F6E\u8868\uFF0C\u53EF\u5728\u300C\u8BBE\u7F6E\u300D\u9875\u7684 providers JSON \u91CC\u6309 URL + \u6A21\u578B\u7CBE\u786E\u8986\u76D6\u3002</p>
48140
+ <section id="page-routing" class="page"><h1>\u5BA2\u6237\u7AEF\u63A5\u5165</h1><p class="lead">\u4E24\u7C7B\u63A5\u5165\uFF1AA. \u63A8\u8350\u63A5\u5165\u65B9\u5F0F\uFF08<span class="mono">/bili/</span> \u524D\u7F00\uFF0C\u96F6\u914D\u7F6E\uFF09\uFF1BB. \u8BC1\u4E66\u63A5\u5165\u65B9\u5F0F\uFF08MITM\uFF0C\u9700\u4FE1\u4EFB CA \u8BC1\u4E66\uFF09\u3002</p>
48141
+ <h2 class="section-title">A. \u63A8\u8350\u63A5\u5165\u65B9\u5F0F\uFF08/bili/ \u524D\u7F00\uFF09</h2>
48142
+ <p class="hint">\u5728\u5BA2\u6237\u7AEF\u914D\u7F6E\u91CC\u628A\u539F baseURL \u524D\u52A0\u4E0A <span class="mono">${origin}/bili/</span> \u5373\u53EF\uFF0C\u5176\u4F59\u4E0D\u53D8\u3002</p>
48149
48143
  <div class="card"><div class="card-head"><h2>OpenCode</h2><button class="btn small copy-btn" data-copy="${origin}/bili/https://open.bigmodel.cn/api/coding/paas/v4">\u590D\u5236</button></div><dl class="kv"><dt>\u914D\u7F6E\u6587\u4EF6</dt><dd class="mono">~/.config/opencode/opencode.json</dd><dt>baseURL</dt><dd class="mono">${origin}/bili/https://open.bigmodel.cn/api/coding/paas/v4</dd></dl></div>
48150
48144
  <div class="card"><div class="card-head"><h2>Codex\uFF08API key\uFF09</h2><button class="btn small copy-btn" data-copy="${origin}/bili/https://api.openai.com/v1">\u590D\u5236</button></div><dl class="kv"><dt>\u914D\u7F6E\u6587\u4EF6</dt><dd class="mono">~/.codex/config.toml</dd><dt>base_url</dt><dd class="mono">${origin}/bili/https://api.openai.com/v1</dd></dl></div>
48151
- <div class="card"><div class="card-head"><h2>Pi</h2><button class="btn small copy-btn" data-copy="${origin}/bili/https://api.anthropic.com">\u590D\u5236</button></div><dl class="kv"><dt>\u914D\u7F6E\u6587\u4EF6</dt><dd class="mono">~/.pi/agent/models.json</dd><dt>baseUrl</dt><dd class="mono">${origin}/bili/https://api.anthropic.com</dd></dl></div>
48145
+ <div class="card"><div class="card-head"><h2>Codex\uFF08ChatGPT \u767B\u5F55\uFF09</h2><button class="btn small copy-btn" data-copy="model_provider = &quot;openai&quot;&#10;openai_base_url = &quot;${origin}/bili/https://chatgpt.com/backend-api/codex&quot;">\u590D\u5236\u914D\u7F6E</button></div><dl class="kv"><dt>\u914D\u7F6E\u6587\u4EF6</dt><dd class="mono">~/.codex/config.toml</dd><dt>\u914D\u7F6E</dt><dd class="mono">model_provider = "openai"<br>openai_base_url = "${origin}/bili/https://chatgpt.com/backend-api/codex"</dd><dt>\u8BA4\u8BC1</dt><dd><span class="mono">codex login</span>\uFF08OAuth token \u900F\u4F20\uFF0C\u65E0\u9700\u8BC1\u4E66\uFF09</dd></dl></div>
48152
48146
  <div class="card"><div class="card-head"><h2>Claude Code\uFF08API key\uFF09</h2><button class="btn small copy-btn" data-copy="${origin}/bili/https://api.anthropic.com">\u590D\u5236</button></div><dl class="kv"><dt>\u73AF\u5883\u53D8\u91CF</dt><dd class="mono">ANTHROPIC_BASE_URL=${origin}/bili/https://api.anthropic.com</dd></dl></div>
48153
- <div class="card"><div class="card-head"><h2>\u901A\u7528\uFF08Cursor / Aider / Continue\uFF09</h2><button class="btn small copy-btn" data-copy="${origin}/bili/">\u590D\u5236</button></div><dl class="kv"><dt>\u89C4\u5219</dt><dd>\u4EFB\u610F baseURL \u524D\u52A0 <span class="mono">${origin}/bili/</span></dd><dt>\u793A\u4F8B</dt><dd class="mono">${origin}/bili/https://api.openai.com/v1</dd></dl></div>
48154
- <h2 class="section-title">B. \u8BA2\u9605/\u767B\u5F55\u5BA2\u6237\u7AEF\uFF08MITM / \u8DEF\u7531\u63A5\u7BA1\uFF09</h2>
48155
- <p class="hint">\u786C\u7F16\u7801 endpoint \u7684 OAuth \u5BA2\u6237\u7AEF\u65E0\u6CD5\u6539 baseURL\uFF0C\u9700 MITM \u900F\u660E\u4EE3\u7406\u6216\u8DEF\u7531\u63A5\u7BA1\u3002</p>
48156
- <div class="card"><div class="card-head"><h2>Codex\uFF08ChatGPT \u8BA2\u9605\uFF09</h2><span id="route-badge" class="badge">\u52A0\u8F7D\u4E2D</span></div><dl class="kv"><dt>\u65B9\u5F0F</dt><dd>\u672C\u5730\u8DEF\u7531\u63A5\u7BA1\uFF08\u81EA\u52A8\u6539\u5199 config.toml\uFF09</dd><dt>\u5F53\u524D Provider</dt><dd id="route-provider" class="mono">\u2014</dd><dt>\u771F\u5B9E\u7AEF\u70B9</dt><dd id="route-real" class="mono">\u2014</dd><dt>\u8DEF\u7531\u7AEF\u70B9</dt><dd id="route-local" class="mono">\u2014</dd></dl><div id="route-detail" class="notice"></div><div class="actions"><button id="route-toggle" class="btn primary">\u542F\u7528 Codex \u8DEF\u7531</button><button id="copy-route" class="btn">\u590D\u5236\u5730\u5740</button></div></div>
48157
- <div class="card"><div class="card-head"><h2>Claude Code\uFF08\u8BA2\u9605\uFF09</h2><span class="badge">MITM</span></div><dl class="kv"><dt>\u65B9\u5F0F</dt><dd>MITM \u900F\u660E\u4EE3\u7406\uFF08\u62E6\u622A HTTPS\uFF09</dd><dt>HTTP Proxy</dt><dd class="mono">${origin}</dd><dt>\u73AF\u5883\u53D8\u91CF</dt><dd class="mono">HTTPS_PROXY=${origin}<br>NODE_EXTRA_CA_CERTS=~/.local/share/billion-context/ca/root-ca.pem</dd></dl></div>
48158
- <div class="card"><div class="card-head"><h2>ZCode\uFF08\u7F16\u7A0B\u5957\u9910\uFF09</h2><span class="badge">MITM</span></div><dl class="kv"><dt>\u65B9\u5F0F</dt><dd>MITM \u900F\u660E\u4EE3\u7406</dd><dt>\u8BBE\u7F6E</dt><dd>Settings \u2192 Network \u2192 HTTP Proxy = <span class="mono">${origin}</span></dd><dt>CA \u8BC1\u4E66</dt><dd class="mono">~/.local/share/billion-context/ca/root-ca.pem</dd></dl></div>
48159
- <div class="card"><div class="card-head"><h2>Codex \u5386\u53F2\u8BB0\u5F55</h2></div><div id="history-summary" class="status">\u52A0\u8F7D\u4E2D</div><div class="actions"><button id="preview-history" class="btn">\u9884\u89C8</button><button id="repair-history" class="btn danger">\u4FEE\u590D\u65E7\u7248\u8DEF\u7531\u4F1A\u8BDD</button></div></div></section>
48147
+ <div class="card"><div class="card-head"><h2>Pi</h2><span class="badge">\u4E0D\u63A8\u8350</span><button class="btn small copy-btn" data-copy="${origin}/bili/https://api.anthropic.com">\u590D\u5236</button></div><dl class="kv"><dt>\u914D\u7F6E\u6587\u4EF6</dt><dd class="mono">~/.pi/agent/models.json</dd><dt>baseUrl</dt><dd class="mono">${origin}/bili/https://api.anthropic.com</dd></dl><p class="hint">\u26A0\uFE0F \u4E0D\u63A8\u8350\uFF1APi \u7684 session \u7BA1\u7406\u4E0E bili \u538B\u7F29\u72B6\u6001\u5B58\u5728\u51B2\u7A81\uFF0C\u8BE6\u89C1 README\u3002</p></div>
48148
+ <div class="card"><div class="card-head"><h2>\u5176\u4ED6\uFF08Cursor / Aider / Continue\uFF09</h2><button class="btn small copy-btn" data-copy="${origin}/bili/">\u590D\u5236</button></div><dl class="kv"><dt>\u89C4\u5219</dt><dd>\u4EFB\u610F baseURL \u524D\u52A0 <span class="mono">${origin}/bili/</span></dd><dt>\u793A\u4F8B</dt><dd class="mono">${origin}/bili/https://api.openai.com/v1</dd></dl></div>
48149
+ <h2 class="section-title">B. \u8BC1\u4E66\u63A5\u5165\u65B9\u5F0F\uFF08zcode \u7B49\u7C7B\u578B\uFF09</h2>
48150
+ <p class="hint">\u4E0D\u652F\u6301\u6539 URL \u7684\u5BA2\u6237\u7AEF\uFF08endpoint \u786C\u7F16\u7801\uFF09\u9700 MITM \u900F\u660E\u4EE3\u7406\uFF0C\u8BBE HTTPS_PROXY + \u4FE1\u4EFB CA \u8BC1\u4E66\u3002</p>
48151
+ <div class="card"><div class="card-head"><h2>ZCode\uFF08\u7F16\u7A0B\u5957\u9910\uFF09</h2><span class="badge">MITM</span></div><dl class="kv"><dt>\u65B9\u5F0F</dt><dd>MITM \u900F\u660E\u4EE3\u7406</dd><dt>\u8BBE\u7F6E</dt><dd>Settings \u2192 Network \u2192 HTTP Proxy = <span class="mono">${origin}</span></dd><dt>CA \u8BC1\u4E66</dt><dd class="mono">~/.local/share/billion-context/ca/root-ca.pem</dd></dl></div></section>
48160
48152
  <section id="page-upstream" class="page"><h1>\u4E0A\u6E38\u7F51\u7EDC</h1><p class="lead">\u63A7\u5236 bili \u5982\u4F55\u8BBF\u95EE\u771F\u5B9E Provider\uFF0C\u4E0D\u4F1A\u6539\u53D8\u5BA2\u6237\u7AEF\u7684\u672C\u5730\u8DEF\u7531\u5730\u5740\u3002</p><div class="card"><div class="card-head"><h2>\u5168\u5C40\u4EE3\u7406</h2></div><div class="modes"><label><input type="radio" name="proxy-mode" value="direct" checked>\u76F4\u8FDE\uFF08\u9ED8\u8BA4\uFF09</label><label><input type="radio" name="proxy-mode" value="manual">\u624B\u52A8\u4EE3\u7406</label><label><input type="radio" name="proxy-mode" value="auto">\u81EA\u52A8\uFF08\u8DDF\u968F\u7CFB\u7EDF\uFF09</label></div><div class="field"><label>HTTP / HTTPS Proxy</label><input id="proxy-url" class="mono" placeholder="http://127.0.0.1:7897"></div><dl class="kv"><dt>\u5F53\u524D\u6765\u6E90</dt><dd id="upstream-source">\u76F4\u8FDE</dd><dt>\u6709\u6548\u4EE3\u7406</dt><dd id="upstream-effective" class="mono">direct</dd><dt>\u7CFB\u7EDF PAC</dt><dd id="upstream-pac" class="mono">\u2014</dd><dt>\u72B6\u6001</dt><dd id="upstream-state" class="status">\u5C1A\u672A\u6D4B\u8BD5</dd></dl><div class="actions"><button id="save-upstream" class="btn primary">\u4FDD\u5B58\u5E76\u70ED\u66F4\u65B0</button><button id="test-upstream" class="btn">\u6D4B\u8BD5\u8FDE\u63A5</button></div></div><div class="card"><div class="card-head"><h2>\u6309 URL \u8986\u76D6</h2></div><p class="status">\u4E3A\u7279\u5B9A Provider \u5355\u72EC\u8BBE\u7F6E\u4E0A\u6E38\u4EE3\u7406\uFF0C\u8986\u76D6\u5168\u5C40\u8BBE\u7F6E\u3002\u7559\u7A7A = \u7EE7\u627F\u5168\u5C40\u3002</p><div id="upstream-overrides" class="status">\u52A0\u8F7D\u4E2D</div><div class="actions"><button id="save-overrides" class="btn primary">\u4FDD\u5B58\u8986\u76D6\u5E76\u70ED\u66F4\u65B0</button></div></div></section>
48161
48153
  <section id="page-sessions" class="page"><div class="card-head"><div><h1>\u4F1A\u8BDD</h1><p class="lead">ACP \u538B\u7F29\u72B6\u6001\u4E0E\u4E0A\u6E38\u7528\u91CF\u3002</p></div><button id="refresh-sessions" class="btn">\u5237\u65B0</button></div><div class="card"><table><thead><tr><th>\u6807\u9898</th><th>\u534F\u8BAE</th><th>\u6807\u8BC6</th><th>\u8BF7\u6C42</th><th>\u4E0A\u4E0B\u6587</th><th>\u6700\u540E\u6D3B\u52A8</th></tr></thead><tbody id="sessions-body"></tbody></table></div></section>
48162
48154
  <section id="page-settings" class="page"><h1>\u9AD8\u7EA7\u8BBE\u7F6E</h1><p class="lead">\u76F4\u63A5\u7F16\u8F91 providers JSON\uFF08\u6DFB\u52A0\u65B0 Provider\u3001\u914D\u7F6E\u6A21\u578B\u4E0A\u4E0B\u6587\u7A97\u53E3\u3001\u6309 URL \u4EE3\u7406\u7B49\uFF09\uFF1B\u4FDD\u5B58\u540E\u7ACB\u5373\u70ED\u66F4\u65B0\u3002</p><div class="card"><div class="field"><label>providers JSON</label><textarea id="providers-json">{}</textarea></div><div class="actions"><button id="save-providers" class="btn primary">\u4FDD\u5B58\u5E76\u5E94\u7528</button></div></div></section></main></div><div id="toast" class="toast"></div><script>${WEB_CLIENT}</script></body></html>`;
48163
48155
  }
48164
48156
 
48165
48157
  // src/web/api.ts
48166
- import { closeSync, fsyncSync, mkdirSync as mkdirSync6, openSync, renameSync as renameSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
48158
+ import { closeSync, fsyncSync, mkdirSync as mkdirSync5, openSync, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
48167
48159
  import { dirname as dirname4 } from "path";
48168
- import { randomUUID as randomUUID2 } from "crypto";
48169
-
48170
- // src/codex-history.ts
48171
- import {
48172
- copyFileSync,
48173
- existsSync as existsSync5,
48174
- mkdirSync as mkdirSync5,
48175
- readFileSync as readFileSync4,
48176
- readdirSync,
48177
- renameSync as renameSync3,
48178
- rmSync,
48179
- statSync as statSync2,
48180
- writeFileSync as writeFileSync4
48181
- } from "fs";
48182
- import path6 from "path";
48183
- import { createHash as createHash3, randomUUID } from "crypto";
48184
- import { homedir as homedir3 } from "os";
48185
-
48186
- // src/codex-provider.ts
48187
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
48188
- import { homedir as homedir2 } from "os";
48189
- import path5 from "path";
48190
- var CODEX_DEFAULT_PROVIDER_ID = "openai";
48191
- var BUILTIN_PROVIDERS = {
48192
- openai: {
48193
- name: "OpenAI",
48194
- baseUrl: "https://chatgpt.com/backend-api/codex",
48195
- wireApi: "responses",
48196
- requiresOpenaiAuth: true,
48197
- supportsWebsockets: true
48198
- }
48199
- };
48200
- function linesOf(text) {
48201
- const lines = [];
48202
- const re2 = /[^\r\n]*(?:\r\n|\r|\n|$)/g;
48203
- for (; ; ) {
48204
- const match = re2.exec(text);
48205
- if (!match || match[0] === "") break;
48206
- const raw = match[0];
48207
- const eol = raw.match(/(\r\n|\r|\n)$/)?.[1] ?? "";
48208
- const content = eol ? raw.slice(0, -eol.length) : raw;
48209
- lines.push({
48210
- start: match.index,
48211
- contentEnd: match.index + content.length,
48212
- end: match.index + raw.length,
48213
- content,
48214
- eol
48215
- });
48216
- if (match.index + raw.length >= text.length) break;
48217
- }
48218
- return lines;
48219
- }
48220
- function preferredEol(text) {
48221
- return text.match(/\r\n|\r|\n/)?.[0] ?? (process.platform === "win32" ? "\r\n" : "\n");
48222
- }
48223
- function parseTomlString(raw) {
48224
- if (raw.startsWith('"') && raw.endsWith('"')) {
48225
- try {
48226
- const parsed = JSON.parse(raw);
48227
- return typeof parsed === "string" ? parsed : void 0;
48228
- } catch {
48229
- return void 0;
48230
- }
48231
- }
48232
- if (raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1);
48233
- return void 0;
48234
- }
48235
- function parseProviderId(raw) {
48236
- const trimmed = raw.trim();
48237
- const quoted = parseTomlString(trimmed);
48238
- if (quoted !== void 0) return quoted;
48239
- return /^[A-Za-z0-9_-]+$/.test(trimmed) ? trimmed : void 0;
48240
- }
48241
- function sections(text) {
48242
- const found = [];
48243
- const lines = linesOf(text);
48244
- for (let index = 0; index < lines.length; index++) {
48245
- const line = lines[index];
48246
- const match = line.content.match(/^\s*\[\s*model_providers\s*\.\s*("(?:\\.|[^"\\])*"|'[^']*'|[A-Za-z0-9_-]+)\s*\]\s*(?:#.*)?$/);
48247
- if (!match) continue;
48248
- const id = parseProviderId(match[1]);
48249
- if (!id) continue;
48250
- let end = text.length;
48251
- for (let next = index + 1; next < lines.length; next++) {
48252
- if (/^\s*\[/.test(lines[next].content)) {
48253
- end = lines[next].start;
48254
- break;
48255
- }
48256
- }
48257
- found.push({ id, headerStart: line.start, bodyStart: line.end, end });
48258
- }
48259
- return found;
48260
- }
48261
- function assignmentInRange(text, key, start, end) {
48262
- const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
48263
- const pattern = new RegExp(`^(\\s*${escaped}\\s*=\\s*)("(?:\\\\.|[^"\\\\])*"|'[^']*'|true|false)(\\s*(?:#.*)?)$`);
48264
- for (const line of linesOf(text)) {
48265
- if (line.start < start || line.start >= end) continue;
48266
- const match = line.content.match(pattern);
48267
- if (!match) continue;
48268
- const raw = match[2];
48269
- const value = raw === "true" ? true : raw === "false" ? false : parseTomlString(raw);
48270
- if (value === void 0) continue;
48271
- const valueStart = line.start + match[1].length;
48272
- return {
48273
- value,
48274
- encoded: raw,
48275
- start: valueStart,
48276
- end: valueStart + raw.length,
48277
- lineStart: line.start,
48278
- lineEnd: line.end
48279
- };
48280
- }
48281
- return void 0;
48282
- }
48283
- function rootAssignment(text, key) {
48284
- const firstSection = linesOf(text).find((line) => /^\s*\[/.test(line.content));
48285
- return assignmentInRange(text, key, 0, firstSection?.start ?? text.length);
48286
- }
48287
- function sectionFor(text, providerId) {
48288
- return sections(text).find((section) => section.id === providerId);
48289
- }
48290
- function fieldFor(text, providerId, key) {
48291
- const section = sectionFor(text, providerId);
48292
- return section ? assignmentInRange(text, key, section.bodyStart, section.end) : void 0;
48293
- }
48294
- function encodeValue(value) {
48295
- return typeof value === "boolean" ? String(value) : JSON.stringify(value);
48296
- }
48297
- function codexConfigFile(env = process.env) {
48298
- const codexHome2 = env.CODEX_HOME?.trim();
48299
- return path5.join(codexHome2 ? path5.resolve(codexHome2) : path5.join(homedir2(), ".codex"), "config.toml");
48300
- }
48301
- function readCodexConfig(configPath = codexConfigFile()) {
48302
- return existsSync4(configPath) ? readFileSync3(configPath, "utf8") : "";
48303
- }
48304
- function resolveActiveCodexProvider(configPath = codexConfigFile()) {
48305
- const text = readCodexConfig(configPath);
48306
- const providerAssignment = rootAssignment(text, "model_provider");
48307
- if (providerAssignment && typeof providerAssignment.value !== "string") {
48308
- throw new Error("Codex model_provider is not a string");
48309
- }
48310
- const explicit = typeof providerAssignment?.value === "string";
48311
- const id = explicit ? providerAssignment.value : CODEX_DEFAULT_PROVIDER_ID;
48312
- const section = sectionFor(text, id);
48313
- const builtin = BUILTIN_PROVIDERS[id];
48314
- if (!section && !builtin) {
48315
- throw new Error(`cannot safely resolve active Codex provider "${id}": [model_providers.${id}] is missing`);
48316
- }
48317
- const stringField = (key, fallback) => {
48318
- const assignment = fieldFor(text, id, key);
48319
- if (assignment && typeof assignment.value !== "string") {
48320
- throw new Error(`Codex provider "${id}" has a non-string ${key}`);
48321
- }
48322
- return typeof assignment?.value === "string" ? assignment.value : fallback;
48323
- };
48324
- const boolField = (key, fallback) => {
48325
- const assignment = fieldFor(text, id, key);
48326
- if (assignment && typeof assignment.value !== "boolean") {
48327
- throw new Error(`Codex provider "${id}" has a non-boolean ${key}`);
48328
- }
48329
- return typeof assignment?.value === "boolean" ? assignment.value : fallback;
48330
- };
48331
- const baseUrl = stringField("base_url", builtin?.baseUrl)?.replace(/\/+$/, "");
48332
- const wireApi = stringField("wire_api", builtin?.wireApi);
48333
- const requiresOpenaiAuth = boolField("requires_openai_auth", builtin?.requiresOpenaiAuth ?? false);
48334
- const supportsWebsockets = boolField("supports_websockets", builtin?.supportsWebsockets ?? false);
48335
- if (!baseUrl || !wireApi) {
48336
- throw new Error(`cannot safely resolve active Codex provider "${id}": base_url/wire_api fields are incomplete`);
48337
- }
48338
- let parsed;
48339
- try {
48340
- parsed = new URL(baseUrl);
48341
- } catch {
48342
- throw new Error(`Codex provider "${id}" has an invalid base_url`);
48343
- }
48344
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
48345
- throw new Error(`Codex provider "${id}" base_url must use http:// or https://`);
48346
- }
48347
- if (parsed.search || parsed.hash) {
48348
- throw new Error(`Codex provider "${id}" base_url cannot contain a query string or fragment`);
48349
- }
48350
- return {
48351
- id,
48352
- explicit,
48353
- sectionExists: !!section,
48354
- name: stringField("name", builtin?.name),
48355
- baseUrl,
48356
- wireApi,
48357
- requiresOpenaiAuth,
48358
- supportsWebsockets,
48359
- configPath
48360
- };
48361
- }
48362
- function getCodexProviderField(text, providerId, key) {
48363
- return fieldFor(text, providerId, key)?.value;
48364
- }
48365
- function getCodexProviderFieldSnapshot(text, providerId, key) {
48366
- const field = fieldFor(text, providerId, key);
48367
- return field ? { value: field.value, encoded: field.encoded } : void 0;
48368
- }
48369
- function setCodexProviderField(text, providerId, key, value) {
48370
- const existing = fieldFor(text, providerId, key);
48371
- if (existing) {
48372
- return {
48373
- text: text.slice(0, existing.start) + encodeValue(value) + text.slice(existing.end),
48374
- added: false
48375
- };
48376
- }
48377
- const section = sectionFor(text, providerId);
48378
- if (!section) throw new Error(`Codex provider section "${providerId}" is missing`);
48379
- const eol = preferredEol(text);
48380
- const prefix = section.end > 0 && !/(?:\r\n|\r|\n)$/.test(text.slice(0, section.end)) ? eol : "";
48381
- const line = `${key} = ${encodeValue(value)}${eol}`;
48382
- return { text: text.slice(0, section.end) + prefix + line + text.slice(section.end), added: true };
48383
- }
48384
- function restoreCodexProviderField(text, providerId, key, snapshot) {
48385
- const existing = fieldFor(text, providerId, key);
48386
- if (!existing) throw new Error(`Codex provider field "${providerId}.${key}" is missing`);
48387
- return text.slice(0, existing.start) + snapshot.encoded + text.slice(existing.end);
48388
- }
48389
- function removeCodexProviderField(text, providerId, key) {
48390
- const existing = fieldFor(text, providerId, key);
48391
- return existing ? text.slice(0, existing.lineStart) + text.slice(existing.lineEnd) : text;
48392
- }
48393
- function appendCodexProviderSection(text, providerId, fields) {
48394
- if (sectionFor(text, providerId)) throw new Error(`Codex provider section "${providerId}" already exists`);
48395
- const eol = preferredEol(text);
48396
- const separator = text.length === 0 ? "" : /(?:\r\n|\r|\n)$/.test(text) ? eol : eol + eol;
48397
- const body = Object.entries(fields).map(([key, value]) => `${key} = ${encodeValue(value)}`).join(eol);
48398
- return `${text}${separator}[model_providers.${providerId}]${eol}${body}${eol}`;
48399
- }
48400
- function removeCodexProviderSectionIfEmpty(text, providerId) {
48401
- const section = sectionFor(text, providerId);
48402
- if (!section) return text;
48403
- const body = text.slice(section.bodyStart, section.end);
48404
- const meaningful = body.split(/\r\n|\r|\n/).some((line) => {
48405
- const trimmed = line.trim();
48406
- return trimmed.length > 0;
48407
- });
48408
- if (meaningful) return text;
48409
- return text.slice(0, section.headerStart) + text.slice(section.end);
48410
- }
48411
- function removeCodexProviderSectionWithPrefixIfEmpty(text, providerId, prefixLength) {
48412
- const section = sectionFor(text, providerId);
48413
- if (!section) return text;
48414
- const withoutSection = removeCodexProviderSectionIfEmpty(text, providerId);
48415
- if (withoutSection === text) return text;
48416
- const start = Math.max(0, section.headerStart - prefixLength);
48417
- return text.slice(0, start) + text.slice(section.end);
48418
- }
48419
-
48420
- // src/codex-history.ts
48421
- var LEGACY_BILI_CODEX_PROVIDER_IDS = ["billion-context-codex", "bili_chatgpt"];
48422
- function codexHome(configPath) {
48423
- return path6.dirname(configPath);
48424
- }
48425
- function collectJsonlFiles(root, current = root, depth = 0) {
48426
- if (depth > 10 || !existsSync5(current)) return [];
48427
- const out = [];
48428
- for (const entry of readdirSync(current, { withFileTypes: true })) {
48429
- const filePath = path6.join(current, entry.name);
48430
- if (entry.isDirectory()) out.push(...collectJsonlFiles(root, filePath, depth + 1));
48431
- else if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push(filePath);
48432
- }
48433
- return out;
48434
- }
48435
- function legacySessionMeta(line) {
48436
- if (!line.includes('"session_meta"') || !line.includes('"model_provider"')) return false;
48437
- try {
48438
- const value = JSON.parse(line);
48439
- if (value.type !== "session_meta" || !value.payload || typeof value.payload !== "object") return false;
48440
- const provider = value.payload.model_provider;
48441
- return typeof provider === "string" && LEGACY_BILI_CODEX_PROVIDER_IDS.includes(provider);
48442
- } catch {
48443
- return false;
48444
- }
48445
- }
48446
- function sessionFiles(configPath) {
48447
- const root = codexHome(configPath);
48448
- const files = [
48449
- ...collectJsonlFiles(path6.join(root, "sessions")),
48450
- ...collectJsonlFiles(path6.join(root, "archived_sessions"))
48451
- ];
48452
- return files.flatMap((filePath) => {
48453
- const before = statSync2(filePath);
48454
- const matches = readFileSync4(filePath, "utf8").split(/\r\n|\r|\n/).filter(legacySessionMeta).length;
48455
- const after = statSync2(filePath);
48456
- if (before.size !== after.size || before.mtimeMs !== after.mtimeMs) {
48457
- throw new Error(`Codex session file changed while being inspected: ${filePath}`);
48458
- }
48459
- return matches > 0 ? [{
48460
- path: filePath,
48461
- relative: path6.relative(root, filePath),
48462
- matches,
48463
- size: after.size,
48464
- mtimeMs: after.mtimeMs,
48465
- mode: after.mode
48466
- }] : [];
48467
- });
48468
- }
48469
- function parseTopLevelString(configText, key) {
48470
- const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
48471
- for (const line of configText.split(/\r\n|\r|\n/)) {
48472
- if (/^\s*\[/.test(line)) break;
48473
- const match = line.match(new RegExp(`^\\s*${escaped}\\s*=\\s*("(?:\\\\.|[^"\\\\])*"|'[^']*')\\s*(?:#.*)?$`));
48474
- if (!match) continue;
48475
- if (match[1].startsWith("'")) return match[1].slice(1, -1);
48476
- try {
48477
- const value = JSON.parse(match[1]);
48478
- return typeof value === "string" ? value : void 0;
48479
- } catch {
48480
- return void 0;
48481
- }
48482
- }
48483
- return void 0;
48484
- }
48485
- function resolveUserPath(value) {
48486
- if (value === "~") return homedir3();
48487
- if (value.startsWith("~/") || value.startsWith("~\\")) {
48488
- return path6.join(homedir3(), value.slice(2));
48489
- }
48490
- return path6.resolve(value);
48491
- }
48492
- function stateDbPaths(configPath) {
48493
- const home = codexHome(configPath);
48494
- const paths = [path6.join(home, "state_5.sqlite")];
48495
- const configText = readCodexConfig(configPath);
48496
- const configured = parseTopLevelString(configText, "sqlite_home");
48497
- const environment = process.env.CODEX_SQLITE_HOME?.trim();
48498
- const extra = configured ?? environment;
48499
- if (extra) {
48500
- const candidate = path6.join(resolveUserPath(extra), "state_5.sqlite");
48501
- if (!paths.includes(candidate)) paths.push(candidate);
48502
- }
48503
- return paths.filter(existsSync5);
48504
- }
48505
- async function sqliteModule() {
48506
- const specifier = "node:sqlite";
48507
- return await import(specifier);
48508
- }
48509
- function hasProviderColumn(database) {
48510
- return database.prepare("PRAGMA table_info(threads)").all().some((row) => row.name === "model_provider");
48511
- }
48512
- function placeholders(count) {
48513
- return Array.from({ length: count }, () => "?").join(",");
48514
- }
48515
- async function countStateRows(configPath) {
48516
- const dbPaths = stateDbPaths(configPath);
48517
- if (dbPaths.length === 0) return { count: 0, supported: true };
48518
- let sqlite;
48519
- try {
48520
- sqlite = await sqliteModule();
48521
- } catch (error) {
48522
- return { count: 0, supported: false, error: `state DB repair requires a Node runtime with node:sqlite: ${String(error)}` };
48523
- }
48524
- let count = 0;
48525
- try {
48526
- for (const dbPath of dbPaths) {
48527
- const database = new sqlite.DatabaseSync(dbPath);
48528
- try {
48529
- database.exec("PRAGMA busy_timeout = 5000");
48530
- if (!hasProviderColumn(database)) continue;
48531
- const row = database.prepare(
48532
- `SELECT COUNT(*) AS count FROM threads WHERE model_provider IN (${placeholders(LEGACY_BILI_CODEX_PROVIDER_IDS.length)})`
48533
- ).get(...LEGACY_BILI_CODEX_PROVIDER_IDS);
48534
- const value = row?.count;
48535
- if (typeof value === "number") count += value;
48536
- else if (typeof value === "bigint") count += Number(value);
48537
- } finally {
48538
- database.close();
48539
- }
48540
- }
48541
- return { count, supported: true };
48542
- } catch (error) {
48543
- return { count, supported: false, error: String(error) };
48544
- }
48545
- }
48546
- async function previewLegacyCodexHistory(configPath = codexConfigFile()) {
48547
- const provider = resolveActiveCodexProvider(configPath);
48548
- const files = sessionFiles(configPath);
48549
- const state = await countStateRows(configPath);
48550
- return {
48551
- targetProviderId: provider.id,
48552
- sourceProviderIds: [...LEGACY_BILI_CODEX_PROVIDER_IDS],
48553
- jsonlFiles: files.length,
48554
- sessions: files.reduce((sum, file) => sum + file.matches, 0),
48555
- stateRows: state.count,
48556
- stateDbSupported: state.supported,
48557
- ...state.error ? { stateDbError: state.error } : {}
48558
- };
48559
- }
48560
- function assertSessionFileUnchanged(file) {
48561
- const current = statSync2(file.path);
48562
- if (current.size !== file.size || current.mtimeMs !== file.mtimeMs) {
48563
- throw new Error(`Codex session file changed during repair: ${file.path}`);
48564
- }
48565
- }
48566
- function rewriteSessionFile(file, targetProviderId) {
48567
- assertSessionFileUnchanged(file);
48568
- const before = readFileSync4(file.path, "utf8");
48569
- assertSessionFileUnchanged(file);
48570
- const eol = before.includes("\r\n") ? "\r\n" : "\n";
48571
- const endsWithEol = /(?:\r\n|\r|\n)$/.test(before);
48572
- let changed = false;
48573
- const lines = before.split(/\r\n|\r|\n/);
48574
- if (endsWithEol) lines.pop();
48575
- const after = lines.map((line) => {
48576
- if (!legacySessionMeta(line)) return line;
48577
- const value = JSON.parse(line);
48578
- const payload = value.payload;
48579
- payload.model_provider = targetProviderId;
48580
- changed = true;
48581
- return JSON.stringify(value);
48582
- }).join(eol) + (endsWithEol ? eol : "");
48583
- if (!changed) return false;
48584
- const tempPath = `${file.path}.${process.pid}.${randomUUID()}.tmp`;
48585
- try {
48586
- writeFileSync4(tempPath, after, { encoding: "utf8", mode: file.mode });
48587
- assertSessionFileUnchanged(file);
48588
- renameSync3(tempPath, file.path);
48589
- } catch (error) {
48590
- rmSync(tempPath, { force: true });
48591
- throw error;
48592
- }
48593
- return true;
48594
- }
48595
- function stateBackupPath(backupPath, configPath, dbPath) {
48596
- const root = codexHome(configPath);
48597
- const relative = path6.relative(root, dbPath);
48598
- if (relative && !relative.startsWith(`..${path6.sep}`) && relative !== ".." && !path6.isAbsolute(relative)) {
48599
- return path6.join(backupPath, "state", relative);
48600
- }
48601
- const digest = createHash3("sha256").update(path6.resolve(dbPath)).digest("hex").slice(0, 16);
48602
- return path6.join(backupPath, "state", `external-${digest}`, path6.basename(dbPath));
48603
- }
48604
- async function repairLegacyCodexHistory(configPath = codexConfigFile()) {
48605
- const preview = await previewLegacyCodexHistory(configPath);
48606
- if (!preview.stateDbSupported) throw new Error(preview.stateDbError ?? "Codex state DB cannot be repaired safely");
48607
- const files = sessionFiles(configPath);
48608
- const databases = stateDbPaths(configPath);
48609
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
48610
- const backupPath = path6.join(dataDir(), "backups", "codex-history-repair", stamp);
48611
- mkdirSync5(backupPath, { recursive: true });
48612
- for (const file of files) {
48613
- assertSessionFileUnchanged(file);
48614
- const destination = path6.join(backupPath, "jsonl", file.relative);
48615
- mkdirSync5(path6.dirname(destination), { recursive: true });
48616
- copyFileSync(file.path, destination);
48617
- assertSessionFileUnchanged(file);
48618
- }
48619
- const sqlite = databases.length > 0 ? await sqliteModule() : void 0;
48620
- const opened = [];
48621
- try {
48622
- if (sqlite) {
48623
- for (const dbPath of databases) {
48624
- const database = new sqlite.DatabaseSync(dbPath);
48625
- opened.push({ database, path: dbPath });
48626
- database.exec("PRAGMA busy_timeout = 5000");
48627
- const destination = stateBackupPath(backupPath, configPath, dbPath);
48628
- mkdirSync5(path6.dirname(destination), { recursive: true });
48629
- await sqlite.backup(database, destination);
48630
- }
48631
- }
48632
- let migratedJsonlFiles = 0;
48633
- for (const file of files) if (rewriteSessionFile(file, preview.targetProviderId)) migratedJsonlFiles++;
48634
- let migratedStateRows = 0;
48635
- for (const entry of opened) {
48636
- if (!hasProviderColumn(entry.database)) continue;
48637
- entry.database.exec("BEGIN IMMEDIATE");
48638
- try {
48639
- const result = entry.database.prepare(
48640
- `UPDATE threads SET model_provider = ? WHERE model_provider IN (${placeholders(LEGACY_BILI_CODEX_PROVIDER_IDS.length)})`
48641
- ).run(preview.targetProviderId, ...LEGACY_BILI_CODEX_PROVIDER_IDS);
48642
- migratedStateRows += Number(result.changes);
48643
- entry.database.exec("COMMIT");
48644
- } catch (error) {
48645
- entry.database.exec("ROLLBACK");
48646
- throw error;
48647
- }
48648
- }
48649
- return { ...preview, backupPath, migratedJsonlFiles, migratedStateRows };
48650
- } catch (error) {
48651
- throw new Error(`Codex history repair stopped; backups are at ${backupPath}: ${String(error)}`, { cause: error });
48652
- } finally {
48653
- for (const entry of opened) entry.database.close();
48654
- if (files.length === 0 && databases.length === 0) rmSync(backupPath, { recursive: true, force: true });
48655
- }
48656
- }
48657
-
48658
- // src/web/api.ts
48160
+ import { randomUUID } from "crypto";
48659
48161
  function readConfig() {
48660
48162
  const parsed = safeReadJson(configFile());
48661
48163
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
@@ -48673,16 +48175,16 @@ function readUpstreamSettings() {
48673
48175
  }
48674
48176
  function atomicWriteConfig(config) {
48675
48177
  const filePath = configFile();
48676
- mkdirSync6(dirname4(filePath), { recursive: true });
48677
- const tempPath = `${filePath}.${process.pid}.${randomUUID2()}.tmp`;
48178
+ mkdirSync5(dirname4(filePath), { recursive: true });
48179
+ const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
48678
48180
  let descriptor;
48679
48181
  try {
48680
48182
  descriptor = openSync(tempPath, "wx", 384);
48681
- writeFileSync5(descriptor, JSON.stringify(config, null, 2) + "\n", "utf8");
48183
+ writeFileSync4(descriptor, JSON.stringify(config, null, 2) + "\n", "utf8");
48682
48184
  fsyncSync(descriptor);
48683
48185
  closeSync(descriptor);
48684
48186
  descriptor = void 0;
48685
- renameSync4(tempPath, filePath);
48187
+ renameSync3(tempPath, filePath);
48686
48188
  } catch (error) {
48687
48189
  if (descriptor !== void 0) closeSync(descriptor);
48688
48190
  try {
@@ -48765,24 +48267,6 @@ async function handleConfigPut(req, res, onChanged, biliPort = 8787) {
48765
48267
  res.writeHead(200, { "content-type": "application/json" });
48766
48268
  res.end(JSON.stringify({ ok: true, providers: hasProviders ? Object.keys(routes).length : void 0 }));
48767
48269
  }
48768
- async function handleCodexHistoryGet(res) {
48769
- try {
48770
- const preview = await previewLegacyCodexHistory();
48771
- res.writeHead(200, { "content-type": "application/json" });
48772
- res.end(JSON.stringify(preview));
48773
- } catch (error) {
48774
- sendError(res, 409, String(error));
48775
- }
48776
- }
48777
- async function handleCodexHistoryRepair(res) {
48778
- try {
48779
- const result = await repairLegacyCodexHistory();
48780
- res.writeHead(200, { "content-type": "application/json" });
48781
- res.end(JSON.stringify(result));
48782
- } catch (error) {
48783
- sendError(res, 409, String(error));
48784
- }
48785
- }
48786
48270
  function sendError(res, status, message) {
48787
48271
  res.writeHead(status, { "content-type": "application/json" });
48788
48272
  res.end(JSON.stringify({ error: message }));
@@ -48816,7 +48300,7 @@ function version() {
48816
48300
  try {
48817
48301
  const here = fileURLToPath(import.meta.url);
48818
48302
  const packagePath = join3(dirname5(here), "..", "..", "package.json");
48819
- return JSON.parse(readFileSync6(packagePath, "utf8")).version ?? "dev";
48303
+ return JSON.parse(readFileSync4(packagePath, "utf8")).version ?? "dev";
48820
48304
  } catch {
48821
48305
  return "dev";
48822
48306
  }
@@ -50206,7 +49690,7 @@ import tls3 from "tls";
50206
49690
  // src/ca.ts
50207
49691
  var import_node_forge = __toESM(require_lib(), 1);
50208
49692
  import fs2 from "fs";
50209
- import path7 from "path";
49693
+ import path5 from "path";
50210
49694
  import tls2 from "tls";
50211
49695
  var ROOT_CERT_FILE = "root-ca.pem";
50212
49696
  var ROOT_KEY_FILE = "root-ca-key.pem";
@@ -50245,8 +49729,8 @@ function ensureRootCA() {
50245
49729
  if (rootCertPem && rootKeyPem) return;
50246
49730
  const dir = caDir();
50247
49731
  fs2.mkdirSync(dir, { recursive: true });
50248
- const certPath = path7.join(dir, ROOT_CERT_FILE);
50249
- const keyPath = path7.join(dir, ROOT_KEY_FILE);
49732
+ const certPath = path5.join(dir, ROOT_CERT_FILE);
49733
+ const keyPath = path5.join(dir, ROOT_KEY_FILE);
50250
49734
  if (fs2.existsSync(certPath) && fs2.existsSync(keyPath)) {
50251
49735
  rootCertPem = fs2.readFileSync(certPath, "utf8");
50252
49736
  rootKeyPem = fs2.readFileSync(keyPath, "utf8");
@@ -50394,304 +49878,6 @@ function readMitmUpstream(socket) {
50394
49878
  return socket[MITM_UPSTREAM_KEY];
50395
49879
  }
50396
49880
 
50397
- // src/codex-takeover.ts
50398
- import {
50399
- closeSync as closeSync2,
50400
- existsSync as existsSync7,
50401
- fsyncSync as fsyncSync2,
50402
- mkdirSync as mkdirSync7,
50403
- openSync as openSync2,
50404
- readFileSync as readFileSync7,
50405
- renameSync as renameSync5,
50406
- statSync as statSync3,
50407
- unlinkSync as unlinkSync3,
50408
- writeFileSync as writeFileSync6
50409
- } from "fs";
50410
- import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
50411
- import path8 from "path";
50412
- var CODEX_ROUTE_STATE_VERSION = 1;
50413
- var INSTALLED_BASE_FIELDS = {
50414
- name: "OpenAI",
50415
- wire_api: "responses",
50416
- requires_openai_auth: true
50417
- };
50418
- function codexRouteStateFile() {
50419
- const override = process.env.BILI_CODEX_ROUTE_STATE?.trim();
50420
- return override ? path8.resolve(override) : path8.join(dataDir(), "codex-route-state.json");
50421
- }
50422
- function resolvePaths(options = {}) {
50423
- return {
50424
- configPath: options.configPath ?? codexConfigFile(),
50425
- statePath: options.statePath ?? codexRouteStateFile(),
50426
- ownerPid: options.ownerPid
50427
- };
50428
- }
50429
- function sha256(text) {
50430
- return createHash4("sha256").update(text).digest("hex");
50431
- }
50432
- function atomicWrite(filePath, text) {
50433
- mkdirSync7(path8.dirname(filePath), { recursive: true });
50434
- const mode = existsSync7(filePath) ? statSync3(filePath).mode : 384;
50435
- const tempPath = path8.join(path8.dirname(filePath), `.${path8.basename(filePath)}.${process.pid}.${randomUUID3()}.tmp`);
50436
- let fd;
50437
- try {
50438
- fd = openSync2(tempPath, "wx", mode);
50439
- writeFileSync6(fd, text, "utf8");
50440
- fsyncSync2(fd);
50441
- closeSync2(fd);
50442
- fd = void 0;
50443
- renameSync5(tempPath, filePath);
50444
- } catch (error) {
50445
- if (fd !== void 0) closeSync2(fd);
50446
- try {
50447
- unlinkSync3(tempPath);
50448
- } catch {
50449
- }
50450
- throw error;
50451
- }
50452
- }
50453
- function parseState(statePath) {
50454
- if (!existsSync7(statePath)) return void 0;
50455
- try {
50456
- const value = JSON.parse(readFileSync7(statePath, "utf8"));
50457
- if (value.version === CODEX_ROUTE_STATE_VERSION && value.active === true && typeof value.providerId === "string" && typeof value.configPath === "string" && typeof value.original?.baseUrl === "string" && typeof value.installed?.baseUrl === "string" && value.installed.supportsWebsockets === false && value.originalFields && typeof value.originalFields === "object" && Array.isArray(value.insertedFields) && typeof value.sectionPrefixLength === "number" && value.sectionPrefixLength >= 0) return value;
50458
- } catch {
50459
- }
50460
- return void 0;
50461
- }
50462
- function writeState(statePath, state) {
50463
- atomicWrite(statePath, JSON.stringify(state, null, 2) + "\n");
50464
- }
50465
- function removeState(statePath) {
50466
- try {
50467
- unlinkSync3(statePath);
50468
- } catch (error) {
50469
- if (error.code !== "ENOENT") throw error;
50470
- }
50471
- }
50472
- function installedBaseUrl(port) {
50473
- if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`invalid bili port: ${port}`);
50474
- return `http://127.0.0.1:${port}/codex`;
50475
- }
50476
- function isInstalledBaseUrl(value) {
50477
- try {
50478
- const url = new URL(value);
50479
- const host = url.hostname.toLowerCase();
50480
- return url.protocol === "http:" && (host === "127.0.0.1" || host === "localhost" || host === "::1") && url.pathname.replace(/\/+$/, "") === "/codex";
50481
- } catch {
50482
- return false;
50483
- }
50484
- }
50485
- function storedField(text, providerId, key) {
50486
- const snapshot = getCodexProviderFieldSnapshot(text, providerId, key);
50487
- return snapshot === void 0 ? { present: false } : { present: true, ...snapshot };
50488
- }
50489
- function pidAlive(pid) {
50490
- if (!Number.isInteger(pid) || pid <= 0) return false;
50491
- try {
50492
- process.kill(pid, 0);
50493
- return true;
50494
- } catch (error) {
50495
- return error.code === "EPERM";
50496
- }
50497
- }
50498
- function getCodexRouteState(options = {}) {
50499
- return parseState(resolvePaths(options).statePath);
50500
- }
50501
- function getCodexTakeoverStatus(options = {}) {
50502
- const paths = resolvePaths(options);
50503
- const state = parseState(paths.statePath);
50504
- if (!state) {
50505
- try {
50506
- return {
50507
- state: "disabled",
50508
- configPath: paths.configPath,
50509
- statePath: paths.statePath,
50510
- provider: resolveActiveCodexProvider(paths.configPath)
50511
- };
50512
- } catch (error) {
50513
- return {
50514
- state: "disabled",
50515
- configPath: paths.configPath,
50516
- statePath: paths.statePath,
50517
- detail: String(error)
50518
- };
50519
- }
50520
- }
50521
- if (path8.resolve(state.configPath) !== path8.resolve(paths.configPath)) {
50522
- return {
50523
- state: "conflict",
50524
- configPath: paths.configPath,
50525
- statePath: paths.statePath,
50526
- providerId: state.providerId,
50527
- detail: `route state belongs to a different Codex config: ${state.configPath}`
50528
- };
50529
- }
50530
- const text = readCodexConfig(paths.configPath);
50531
- const currentBase = getCodexProviderField(text, state.providerId, "base_url");
50532
- const currentWebsockets = getCodexProviderField(text, state.providerId, "supports_websockets");
50533
- const matches = currentBase === state.installed.baseUrl && currentWebsockets === false;
50534
- const port = Number.parseInt(new URL(state.installed.baseUrl).port, 10);
50535
- return {
50536
- state: matches ? "enabled" : "conflict",
50537
- configPath: paths.configPath,
50538
- statePath: paths.statePath,
50539
- providerId: state.providerId,
50540
- port,
50541
- baseUrl: state.installed.baseUrl,
50542
- originalBaseUrl: state.original.baseUrl,
50543
- lastRequestAt: state.lastRequestAt,
50544
- lastRequestPath: state.lastRequestPath,
50545
- ...matches ? {} : { detail: "managed Codex route fields changed; restore will preserve user edits" }
50546
- };
50547
- }
50548
- function enableCodexTakeover(port, options = {}) {
50549
- const paths = resolvePaths(options);
50550
- const existing = parseState(paths.statePath);
50551
- if (existing) {
50552
- const status = getCodexTakeoverStatus(paths);
50553
- const otherLiveOwner = existing.pid && existing.pid !== paths.ownerPid && pidAlive(existing.pid);
50554
- if (status.state === "enabled" && status.port === port) {
50555
- if (otherLiveOwner && paths.ownerPid) {
50556
- throw new Error(`Codex route is owned by running bili process ${existing.pid}`);
50557
- }
50558
- if (paths.ownerPid && existing.pid !== paths.ownerPid) {
50559
- existing.pid = paths.ownerPid;
50560
- writeState(paths.statePath, existing);
50561
- return getCodexTakeoverStatus(paths);
50562
- }
50563
- return status;
50564
- }
50565
- if (otherLiveOwner) {
50566
- throw new Error(`Codex route is owned by running bili process ${existing.pid}`);
50567
- }
50568
- disableCodexTakeover(paths);
50569
- }
50570
- const provider = resolveActiveCodexProvider(paths.configPath);
50571
- if (provider.wireApi !== "responses") {
50572
- throw new Error(`Codex provider "${provider.id}" uses wire_api=${provider.wireApi}; local route requires responses`);
50573
- }
50574
- if (isInstalledBaseUrl(provider.baseUrl)) {
50575
- throw new Error("Codex provider already points at a local /codex route without billion-context ownership state");
50576
- }
50577
- const before = readCodexConfig(paths.configPath);
50578
- const installedUrl = installedBaseUrl(port);
50579
- const managedKeys = ["base_url", "supports_websockets"];
50580
- const originalFields = {};
50581
- for (const key of managedKeys) originalFields[key] = storedField(before, provider.id, key);
50582
- let after = before;
50583
- const insertedFields = [];
50584
- let sectionAdded = false;
50585
- if (!provider.sectionExists) {
50586
- sectionAdded = true;
50587
- const fields = {
50588
- ...INSTALLED_BASE_FIELDS,
50589
- base_url: installedUrl,
50590
- supports_websockets: false
50591
- };
50592
- after = appendCodexProviderSection(after, provider.id, fields);
50593
- insertedFields.push(...Object.keys(fields));
50594
- } else {
50595
- const baseResult = setCodexProviderField(after, provider.id, "base_url", installedUrl);
50596
- after = baseResult.text;
50597
- if (baseResult.added) insertedFields.push("base_url");
50598
- const websocketResult = setCodexProviderField(after, provider.id, "supports_websockets", false);
50599
- after = websocketResult.text;
50600
- if (websocketResult.added) insertedFields.push("supports_websockets");
50601
- }
50602
- const state = {
50603
- version: CODEX_ROUTE_STATE_VERSION,
50604
- active: true,
50605
- ...paths.ownerPid ? { pid: paths.ownerPid } : {},
50606
- providerId: provider.id,
50607
- configPath: paths.configPath,
50608
- sectionAdded,
50609
- sectionPrefixLength: sectionAdded ? before.length === 0 ? 0 : /(?:\r\n|\r|\n)$/.test(before) ? before.match(/(\r\n|\r|\n)$/)?.[0].length ?? 1 : 2 * (before.match(/\r\n|\r|\n/)?.[0].length ?? (process.platform === "win32" ? 2 : 1)) : 0,
50610
- insertedFields,
50611
- originalFields,
50612
- original: { baseUrl: provider.baseUrl, supportsWebsockets: provider.supportsWebsockets },
50613
- installed: { baseUrl: installedUrl, supportsWebsockets: false },
50614
- configHashBefore: sha256(before),
50615
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
50616
- };
50617
- writeState(paths.statePath, state);
50618
- try {
50619
- atomicWrite(paths.configPath, after);
50620
- } catch (error) {
50621
- removeState(paths.statePath);
50622
- throw error;
50623
- }
50624
- return getCodexTakeoverStatus(paths);
50625
- }
50626
- function disableCodexTakeover(options = {}) {
50627
- const paths = resolvePaths(options);
50628
- const state = parseState(paths.statePath);
50629
- if (!state) return getCodexTakeoverStatus(paths);
50630
- let text = readCodexConfig(state.configPath);
50631
- const preserved = [];
50632
- const installedValues = {
50633
- ...INSTALLED_BASE_FIELDS,
50634
- base_url: state.installed.baseUrl,
50635
- supports_websockets: false
50636
- };
50637
- for (const [key, installedValue] of Object.entries(installedValues)) {
50638
- if (!state.sectionAdded && key !== "base_url" && key !== "supports_websockets") continue;
50639
- const current = getCodexProviderField(text, state.providerId, key);
50640
- if (current !== installedValue) {
50641
- if (current !== void 0) preserved.push(key);
50642
- continue;
50643
- }
50644
- const original = state.originalFields[key];
50645
- if (original?.present && original.value !== void 0) {
50646
- text = typeof original.encoded === "string" ? restoreCodexProviderField(text, state.providerId, key, {
50647
- value: original.value,
50648
- encoded: original.encoded
50649
- }) : setCodexProviderField(text, state.providerId, key, original.value).text;
50650
- } else {
50651
- text = removeCodexProviderField(text, state.providerId, key);
50652
- }
50653
- }
50654
- if (state.sectionAdded) {
50655
- text = removeCodexProviderSectionWithPrefixIfEmpty(text, state.providerId, state.sectionPrefixLength ?? 0);
50656
- }
50657
- atomicWrite(state.configPath, text);
50658
- removeState(paths.statePath);
50659
- const status = getCodexTakeoverStatus({ ...paths, configPath: state.configPath });
50660
- if (preserved.length > 0) status.detail = `\u68C0\u6D4B\u5230\u7528\u6237\u4FEE\u6539\uFF0C\u5DF2\u4FDD\u7559\uFF1A${preserved.join(", ")}`;
50661
- return status;
50662
- }
50663
- function recoverStaleCodexTakeover(options = {}) {
50664
- const paths = resolvePaths(options);
50665
- const state = parseState(paths.statePath);
50666
- if (state?.pid && !pidAlive(state.pid)) return disableCodexTakeover(paths);
50667
- return getCodexTakeoverStatus(paths);
50668
- }
50669
- function claimCodexTakeover(ownerPid = process.pid, options = {}) {
50670
- const paths = resolvePaths(options);
50671
- const state = parseState(paths.statePath);
50672
- if (!state) return getCodexTakeoverStatus(paths);
50673
- if (state.pid && state.pid !== ownerPid && pidAlive(state.pid)) {
50674
- throw new Error(`Codex route is owned by running bili process ${state.pid}`);
50675
- }
50676
- state.pid = ownerPid;
50677
- writeState(paths.statePath, state);
50678
- return getCodexTakeoverStatus(paths);
50679
- }
50680
- function restoreCodexTakeoverOwnedBy(ownerPid = process.pid, options = {}) {
50681
- const paths = resolvePaths(options);
50682
- const state = parseState(paths.statePath);
50683
- if (!state || state.pid !== ownerPid) return getCodexTakeoverStatus(paths);
50684
- return disableCodexTakeover(paths);
50685
- }
50686
- function recordCodexRouteRequest(requestPath, options = {}) {
50687
- const paths = resolvePaths(options);
50688
- const state = parseState(paths.statePath);
50689
- if (!state) return;
50690
- state.lastRequestAt = (/* @__PURE__ */ new Date()).toISOString();
50691
- state.lastRequestPath = requestPath;
50692
- writeState(paths.statePath, state);
50693
- }
50694
-
50695
49881
  // src/content-encoding.ts
50696
49882
  import { promisify } from "util";
50697
49883
  import { brotliDecompress, gunzip, inflate, inflateRaw } from "zlib";
@@ -51427,19 +50613,24 @@ function resolveUpstream(_opts, reqUrl, req) {
51427
50613
  const mitmKey = mitmUpstream.replace(/^https:\/\//, "mitm://");
51428
50614
  return { upstream: mitmUpstream, rewrittenUrl: mitmKey + (reqUrl ?? "") };
51429
50615
  }
51430
- if (reqUrl === "/codex" || reqUrl.startsWith("/codex/") || reqUrl.startsWith("/codex?")) {
51431
- const state = getCodexRouteState();
51432
- if (!state) return void 0;
51433
- const suffix = reqUrl.slice("/codex".length);
51434
- const baseUrl = state.original.baseUrl.replace(/\/+$/, "");
51435
- return { upstream: baseUrl, rewrittenUrl: baseUrl + suffix };
51436
- }
51437
- if (reqUrl.startsWith("/bili/http://") || reqUrl.startsWith("/bili/https://")) {
51438
- const full = reqUrl.slice(6);
51439
- try {
51440
- const u2 = new URL(full);
51441
- return { upstream: `${u2.protocol}//${u2.host}`, rewrittenUrl: full };
51442
- } catch {
50616
+ const KNOWN_PROTOCOLS = ["responses", "anthropic", "openai"];
50617
+ if (reqUrl.startsWith("/bili/")) {
50618
+ let rest = reqUrl.slice(6);
50619
+ let explicitProtocol;
50620
+ for (const p2 of KNOWN_PROTOCOLS) {
50621
+ const prefix = `${p2}/`;
50622
+ if (rest.startsWith(prefix + "http://") || rest.startsWith(prefix + "https://")) {
50623
+ explicitProtocol = p2;
50624
+ rest = rest.slice(prefix.length);
50625
+ break;
50626
+ }
50627
+ }
50628
+ if (rest.startsWith("http://") || rest.startsWith("https://")) {
50629
+ try {
50630
+ const u2 = new URL(rest);
50631
+ return { upstream: `${u2.protocol}//${u2.host}`, rewrittenUrl: rest, explicitProtocol };
50632
+ } catch {
50633
+ }
51443
50634
  }
51444
50635
  }
51445
50636
  return void 0;
@@ -51521,13 +50712,6 @@ async function startServer(opts) {
51521
50712
  if (process.platform === "win32") {
51522
50713
  process.on("SIGBREAK", () => shutdown("SIGBREAK"));
51523
50714
  }
51524
- server.once("close", () => {
51525
- try {
51526
- restoreCodexTakeoverOwnedBy(process.pid);
51527
- } catch (error) {
51528
- log2("warn", `[codex-route] restore on server close failed: ${String(error)}`);
51529
- }
51530
- });
51531
50715
  return server;
51532
50716
  }
51533
50717
  function isLoopback(addr) {
@@ -51593,25 +50777,8 @@ async function handle(req, res, opts, core, config, log2) {
51593
50777
  }, opts.port);
51594
50778
  }
51595
50779
  if (req.method === "POST" && req.url === "/__bili/config/reload") return handleConfigReload(opts, res, log2);
51596
- if (req.method === "GET" && req.url === "/__bili/codex") {
51597
- res.writeHead(200, { "content-type": "application/json" });
51598
- res.end(JSON.stringify(getCodexTakeoverStatus()));
51599
- return;
51600
- }
51601
- if (req.method === "POST" && (req.url === "/__bili/codex/enable" || req.url === "/__bili/codex/disable")) {
51602
- try {
51603
- const status = req.url.endsWith("/enable") ? enableCodexTakeover(opts.port, { ownerPid: process.pid }) : disableCodexTakeover();
51604
- res.writeHead(200, { "content-type": "application/json" });
51605
- res.end(JSON.stringify(status));
51606
- } catch (error) {
51607
- res.writeHead(409, { "content-type": "application/json" });
51608
- res.end(JSON.stringify({ error: String(error) }));
51609
- }
51610
- return;
51611
- }
51612
50780
  if (req.method === "GET" && req.url === "/__bili/upstream") {
51613
- const routeState = getCodexRouteState();
51614
- const target = routeState?.original.baseUrl ?? opts.upstream;
50781
+ const target = opts.upstream;
51615
50782
  const decision = resolveProxyDecision(opts.routes, opts.proxy, target, opts.proxyFallback);
51616
50783
  const connection = getUpstreamConnectionStatus();
51617
50784
  const connectionMatchesTarget = (() => {
@@ -51638,8 +50805,7 @@ async function handle(req, res, opts, core, config, log2) {
51638
50805
  return;
51639
50806
  }
51640
50807
  if (req.method === "POST" && req.url === "/__bili/upstream/test") {
51641
- const routeState = getCodexRouteState();
51642
- const target = routeState?.original.baseUrl ?? opts.upstream;
50808
+ const target = opts.upstream;
51643
50809
  const targetUrl2 = new URL(target).origin;
51644
50810
  const proxyUrl = resolveProxyDecision(opts.routes, opts.proxy, target, opts.proxyFallback).proxy;
51645
50811
  try {
@@ -51658,11 +50824,9 @@ async function handle(req, res, opts, core, config, log2) {
51658
50824
  }
51659
50825
  return;
51660
50826
  }
51661
- if (req.method === "GET" && req.url === "/__bili/codex-history") return handleCodexHistoryGet(res);
51662
- if (req.method === "POST" && req.url === "/__bili/codex-history/repair") return handleCodexHistoryRepair(res);
51663
- if ((req.url === "/codex" || req.url?.startsWith("/codex/") || req.url?.startsWith("/codex?")) && !getCodexRouteState()) {
51664
- res.writeHead(503, { "content-type": "application/json" });
51665
- res.end(JSON.stringify({ error: "Codex local route is not enabled; run bili codex enable" }));
50827
+ if (req.headers.upgrade === "websocket") {
50828
+ res.writeHead(426, { "content-type": "application/json" });
50829
+ res.end(JSON.stringify({ error: "WebSocket upgrades are not supported; use HTTP POST" }));
51666
50830
  return;
51667
50831
  }
51668
50832
  let bodyBuffer;
@@ -51687,12 +50851,9 @@ async function handle(req, res, opts, core, config, log2) {
51687
50851
  const urlPath = url.split("?", 2)[0];
51688
50852
  const responsesCompact = urlPath.endsWith("/responses/compact");
51689
50853
  const countTokens = isCountTokensRequest(req.method ?? "GET", urlPath, bodyBuffer.length > 0);
51690
- const protocol = req.method === "POST" && bodyBuffer.length > 0 ? urlPath.endsWith("/chat/completions") ? "openai" : urlPath.endsWith("/v1/messages") || urlPath.endsWith("/messages") ? "anthropic" : urlPath.endsWith("/responses") || responsesCompact ? "responses" : null : null;
51691
50854
  const route = resolveUpstream(opts, req.url ?? "", req);
51692
- if (route && (req.url === "/codex" || req.url?.startsWith("/codex/") || req.url?.startsWith("/codex?"))) {
51693
- recordCodexRouteRequest(req.url ?? "/codex");
51694
- }
51695
50855
  const upstreamOrigin = route ? route.upstream : opts.upstream;
50856
+ const protocol = route?.explicitProtocol ?? (req.method === "POST" && bodyBuffer.length > 0 ? urlPath.endsWith("/chat/completions") ? "openai" : urlPath.endsWith("/v1/messages") || urlPath.endsWith("/messages") ? "anthropic" : urlPath.endsWith("/responses") || responsesCompact ? "responses" : null : null);
51696
50857
  let parsed = null;
51697
50858
  if (protocol && bodyBuffer.length > 0) {
51698
50859
  try {
@@ -52325,10 +51486,10 @@ async function pipeThrough(stream2, res) {
52325
51486
  }
52326
51487
  }
52327
51488
  async function dumpStreamToFile(stream2, dir, name) {
52328
- const { mkdirSync: mkdirSync8, createWriteStream: createWriteStream2 } = await import("fs");
51489
+ const { mkdirSync: mkdirSync6, createWriteStream: createWriteStream2 } = await import("fs");
52329
51490
  const { join: join4 } = await import("path");
52330
51491
  try {
52331
- mkdirSync8(dir, { recursive: true });
51492
+ mkdirSync6(dir, { recursive: true });
52332
51493
  const ws2 = createWriteStream2(join4(dir, name));
52333
51494
  const reader = stream2.getReader();
52334
51495
  try {
@@ -55397,12 +54558,12 @@ var To = (s3) => {
55397
54558
  };
55398
54559
 
55399
54560
  // src/update.ts
55400
- import path9 from "path";
54561
+ import path6 from "path";
55401
54562
  import { fileURLToPath as fileURLToPath2 } from "url";
55402
54563
  var REGISTRY_BASE = "https://registry.npmjs.org";
55403
54564
  var CHECK_INTERVAL_MS = 3 * 60 * 1e3;
55404
- var THROTTLE_FILE = path9.join(cacheDir(), ".update-check");
55405
- var LOCK_FILE = path9.join(cacheDir(), ".update-lock");
54565
+ var THROTTLE_FILE = path6.join(cacheDir(), ".update-check");
54566
+ var LOCK_FILE = path6.join(cacheDir(), ".update-lock");
55406
54567
  var LOCK_STALE_MS = 2 * 60 * 1e3;
55407
54568
  var timer;
55408
54569
  var inFlight = false;
@@ -55431,27 +54592,27 @@ async function readLastCheck() {
55431
54592
  }
55432
54593
  async function writeLastCheck(ts2) {
55433
54594
  try {
55434
- await mkdir2(path9.dirname(THROTTLE_FILE), { recursive: true });
54595
+ await mkdir2(path6.dirname(THROTTLE_FILE), { recursive: true });
55435
54596
  await writeFile2(THROTTLE_FILE, String(ts2), "utf-8");
55436
54597
  } catch {
55437
54598
  }
55438
54599
  }
55439
54600
  async function findInstallDir(packageName) {
55440
- let dir = path9.dirname(fileURLToPath2(import.meta.url));
54601
+ let dir = path6.dirname(fileURLToPath2(import.meta.url));
55441
54602
  for (; ; ) {
55442
54603
  try {
55443
- const pkg = JSON.parse(await readFile2(path9.join(dir, "package.json"), "utf-8"));
54604
+ const pkg = JSON.parse(await readFile2(path6.join(dir, "package.json"), "utf-8"));
55444
54605
  if (pkg.name === packageName) return dir;
55445
54606
  } catch {
55446
54607
  }
55447
- const parent = path9.dirname(dir);
54608
+ const parent = path6.dirname(dir);
55448
54609
  if (parent === dir) return void 0;
55449
54610
  dir = parent;
55450
54611
  }
55451
54612
  }
55452
54613
  async function readDiskVersion(installDir) {
55453
54614
  try {
55454
- const pkg = JSON.parse(await readFile2(path9.join(installDir, "package.json"), "utf-8"));
54615
+ const pkg = JSON.parse(await readFile2(path6.join(installDir, "package.json"), "utf-8"));
55455
54616
  return pkg.version;
55456
54617
  } catch {
55457
54618
  return void 0;
@@ -55605,14 +54766,14 @@ async function installViaTarball(version2, tarballUrl, installDir) {
55605
54766
  } catch (e) {
55606
54767
  return { ok: false, error: `tarball download failed: ${String(e)}` };
55607
54768
  }
55608
- const tmpFile = path9.join(cacheDir(), `.update-${version2}.tgz`);
54769
+ const tmpFile = path6.join(cacheDir(), `.update-${version2}.tgz`);
55609
54770
  try {
55610
54771
  await mkdir2(cacheDir(), { recursive: true });
55611
54772
  await writeFile2(tmpFile, tgzBuffer);
55612
54773
  } catch (e) {
55613
54774
  return { ok: false, error: `failed to write temp file ${tmpFile}: ${String(e)}` };
55614
54775
  }
55615
- const stagingDir = path9.join(cacheDir(), `.update-staging-${version2}`);
54776
+ const stagingDir = path6.join(cacheDir(), `.update-staging-${version2}`);
55616
54777
  try {
55617
54778
  await rm(stagingDir, { recursive: true, force: true });
55618
54779
  await mkdir2(stagingDir, { recursive: true });
@@ -55655,14 +54816,14 @@ function startAutoUpdate(opts) {
55655
54816
  }
55656
54817
 
55657
54818
  // src/cli.ts
55658
- import { readFileSync as readFileSync8 } from "fs";
54819
+ import { readFileSync as readFileSync5 } from "fs";
55659
54820
  import { fileURLToPath as fileURLToPath3 } from "url";
55660
- import path10 from "path";
54821
+ import path7 from "path";
55661
54822
  var VERSION = (() => {
55662
54823
  try {
55663
54824
  const here = fileURLToPath3(import.meta.url);
55664
- const pkg = path10.join(path10.dirname(here), "..", "package.json");
55665
- return JSON.parse(readFileSync8(pkg, "utf8")).version ?? "dev";
54825
+ const pkg = path7.join(path7.dirname(here), "..", "package.json");
54826
+ return JSON.parse(readFileSync5(pkg, "utf8")).version ?? "dev";
55666
54827
  } catch {
55667
54828
  return "dev";
55668
54829
  }
@@ -55670,8 +54831,8 @@ var VERSION = (() => {
55670
54831
  var PACKAGE_NAME = (() => {
55671
54832
  try {
55672
54833
  const here = fileURLToPath3(import.meta.url);
55673
- const pkg = path10.join(path10.dirname(here), "..", "package.json");
55674
- return JSON.parse(readFileSync8(pkg, "utf8")).name ?? "billion-context";
54834
+ const pkg = path7.join(path7.dirname(here), "..", "package.json");
54835
+ return JSON.parse(readFileSync5(pkg, "utf8")).name ?? "billion-context";
55675
54836
  } catch {
55676
54837
  return "billion-context";
55677
54838
  }
@@ -55680,9 +54841,6 @@ var HELP = `bili ${VERSION} \u2014 billion-context proxy
55680
54841
 
55681
54842
  Usage:
55682
54843
  bili [start] [options] start the proxy (default: reads ${configFile()})
55683
- bili codex enable [--port N] route Codex Desktop through the local proxy
55684
- bili codex disable restore the active Codex provider endpoint
55685
- bili codex status show Codex Desktop local-route status
55686
54844
  bili update check for & install a newer version now
55687
54845
  bili --version print version
55688
54846
  bili --help show this help
@@ -55762,31 +54920,15 @@ function parseArgs(argv) {
55762
54920
  command = command === "help" || command === "version" ? command : "start";
55763
54921
  } else if (cmd === "update") {
55764
54922
  command = "update";
55765
- } else if (cmd === "codex") {
55766
- command = "codex";
55767
54923
  } else {
55768
54924
  console.error(`bili: unknown command "${cmd}" (try "bili --help")`);
55769
54925
  process.exit(2);
55770
54926
  }
55771
54927
  }
55772
- let codexAction;
55773
- if (positional[0] === "codex") {
55774
- command = "codex";
55775
- const action = positional[1];
55776
- if (action !== "enable" && action !== "disable" && action !== "status") {
55777
- console.error('bili: "codex" requires enable, disable, or status');
55778
- process.exit(2);
55779
- }
55780
- if (positional.length > 2) {
55781
- console.error(`bili: unexpected argument "${positional[2]}"`);
55782
- process.exit(2);
55783
- }
55784
- codexAction = action;
55785
- }
55786
- return { command, codexAction, overrides };
54928
+ return { command, overrides };
55787
54929
  }
55788
54930
  async function main() {
55789
- const { command, codexAction, overrides } = parseArgs(process.argv.slice(2));
54931
+ const { command, overrides } = parseArgs(process.argv.slice(2));
55790
54932
  if (command === "help") {
55791
54933
  process.stdout.write(HELP);
55792
54934
  return;
@@ -55802,28 +54944,8 @@ async function main() {
55802
54944
  for (const [k2, v2] of Object.entries(overrides)) {
55803
54945
  if (v2 !== void 0) process.env[k2] = v2;
55804
54946
  }
55805
- if (command === "codex") {
55806
- if (codexAction === "enable") recoverStaleCodexTakeover();
55807
- const status = codexAction === "enable" ? enableCodexTakeover(loadOptions().port) : codexAction === "disable" ? disableCodexTakeover() : getCodexTakeoverStatus();
55808
- if (status.state === "enabled") {
55809
- process.stdout.write(`Codex route enabled for ${status.providerId}: ${status.baseUrl} \u2192 ${status.originalBaseUrl}
55810
- `);
55811
- } else if (status.state === "disabled") {
55812
- const provider = status.provider ? ` (provider ${status.provider.id})` : "";
55813
- process.stdout.write(`Codex route disabled${provider}: ${status.configPath}${status.detail ? `
55814
- ${status.detail}` : ""}
55815
- `);
55816
- } else {
55817
- process.stdout.write(`Codex Desktop local route conflict: ${status.detail ?? status.configPath}
55818
- `);
55819
- process.exitCode = 1;
55820
- }
55821
- return;
55822
- }
55823
- recoverStaleCodexTakeover();
55824
54947
  ensureConfigTemplate();
55825
54948
  const opts = loadOptions();
55826
- claimCodexTakeover(process.pid);
55827
54949
  await startServer(opts);
55828
54950
  if (opts.autoUpdate) {
55829
54951
  startAutoUpdate({ packageName: PACKAGE_NAME, currentVersion: VERSION, autoUpdate: true });