@jeffreycao/copilot-api 2.3.0 → 2.3.1

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@jeffreycao/copilot-api",
4
- "version": "2.3.0",
4
+ "version": "2.3.1",
5
5
  "description": "GitHub Copilot, OpenAI Codex, OpenCode Go, and third-party AI provider gateway with OpenAI and Anthropic API compatibility.",
6
6
  "keywords": [
7
7
  "github-copilot",
package/pages/index.html CHANGED
@@ -407,12 +407,12 @@
407
407
  />
408
408
  </div>
409
409
  <div>
410
- <label for="x-api-key" class="field-label">x-api-key</label>
410
+ <label for="x-api-key" class="field-label">Auth credential</label>
411
411
  <input
412
412
  type="password"
413
413
  id="x-api-key"
414
414
  class="input-control input-focus"
415
- placeholder="Optional when auth is enabled"
415
+ placeholder="x-api-key or Authorization Bearer"
416
416
  autocomplete="off"
417
417
  spellcheck="false"
418
418
  />
@@ -433,6 +433,9 @@
433
433
  <span>Refresh</span>
434
434
  </button>
435
435
  </form>
436
+ <p class="mt-2 text-xs" style="color: var(--color-gray);">
437
+ Credentials are remembered per endpoint origin and are not sent to other endpoints.
438
+ </p>
436
439
  </header>
437
440
 
438
441
  <main id="content-area" class="content-area"></main>
@@ -464,7 +467,8 @@
464
467
  const DEFAULT_TOKEN_USAGE_EVENTS_PAGE_SIZE = 20;
465
468
  const ALL_METRICS_VALUE = "__all__";
466
469
  const ALL_MODELS_VALUE = "__all__";
467
- const API_KEY_STORAGE_KEY = "copilot-api.usage-viewer.x-api-key";
470
+ const LEGACY_API_KEY_STORAGE_KEY = "copilot-api.usage-viewer.x-api-key";
471
+ const CREDENTIAL_STORAGE_PREFIX = "copilot-api.usage-viewer.credential.";
468
472
  const VALID_PERIODS = new Set(["day", "week", "month"]);
469
473
  const EMPTY_TOKEN_USAGE_TOTALS = {
470
474
  cache_creation_input_tokens: 0,
@@ -545,33 +549,156 @@
545
549
  return normalized;
546
550
  }
547
551
 
548
- function loadStoredApiKey() {
552
+ function getEndpointOrigin(endpoint) {
553
+ if (!endpoint) {
554
+ return "";
555
+ }
549
556
  try {
550
- return localStorage.getItem(API_KEY_STORAGE_KEY) || "";
557
+ return new URL(endpoint, window.location.href).origin;
551
558
  } catch (error) {
552
- console.warn("Could not read stored API key:", getErrorMessage(error));
559
+ console.warn("Could not parse endpoint URL:", getErrorMessage(error));
553
560
  return "";
554
561
  }
555
562
  }
556
563
 
557
- function storeApiKey(value) {
564
+ function getCredentialStorageKey(endpoint) {
565
+ const origin = getEndpointOrigin(endpoint);
566
+ return origin ? `${CREDENTIAL_STORAGE_PREFIX}${origin}` : "";
567
+ }
568
+
569
+ function readStoredApiKey(endpoint) {
570
+ const storageKey = getCredentialStorageKey(endpoint);
571
+ if (!storageKey) {
572
+ return "";
573
+ }
558
574
  try {
559
- const trimmed = value.trim();
560
- if (trimmed) {
561
- localStorage.setItem(API_KEY_STORAGE_KEY, trimmed);
562
- } else {
563
- localStorage.removeItem(API_KEY_STORAGE_KEY);
564
- }
575
+ return window.localStorage.getItem(storageKey) || "";
576
+ } catch (error) {
577
+ console.warn(
578
+ "Could not read stored credential:",
579
+ getErrorMessage(error)
580
+ );
581
+ return "";
582
+ }
583
+ }
584
+
585
+ function writeStoredApiKey(endpoint, value) {
586
+ const storageKey = getCredentialStorageKey(endpoint);
587
+ if (!storageKey) {
588
+ return;
589
+ }
590
+ try {
591
+ window.localStorage.setItem(storageKey, value);
565
592
  } catch (error) {
566
- console.warn("Could not store API key:", getErrorMessage(error));
593
+ console.warn(
594
+ "Could not store credential:",
595
+ getErrorMessage(error)
596
+ );
597
+ }
598
+ }
599
+
600
+ function removeStoredApiKey(endpoint) {
601
+ const storageKey = getCredentialStorageKey(endpoint);
602
+ if (!storageKey) {
603
+ return;
604
+ }
605
+ try {
606
+ window.localStorage.removeItem(storageKey);
607
+ } catch (error) {
608
+ console.warn(
609
+ "Could not remove stored credential:",
610
+ getErrorMessage(error)
611
+ );
612
+ }
613
+ }
614
+
615
+ function removeLegacyStoredApiKey() {
616
+ try {
617
+ window.localStorage.removeItem(LEGACY_API_KEY_STORAGE_KEY);
618
+ } catch (error) {
619
+ console.warn(
620
+ "Could not remove legacy credential:",
621
+ getErrorMessage(error)
622
+ );
623
+ }
624
+ }
625
+
626
+ function migrateLegacyApiKey(endpoint) {
627
+ const storageKey = getCredentialStorageKey(endpoint);
628
+ if (storageKey && getEndpointOrigin(endpoint) === window.location.origin) {
629
+ try {
630
+ const legacyValue = window.localStorage.getItem(
631
+ LEGACY_API_KEY_STORAGE_KEY
632
+ );
633
+ if (legacyValue && !window.localStorage.getItem(storageKey)) {
634
+ window.localStorage.setItem(storageKey, legacyValue);
635
+ }
636
+ } catch (error) {
637
+ console.warn(
638
+ "Could not migrate legacy credential:",
639
+ getErrorMessage(error)
640
+ );
641
+ }
642
+ }
643
+ removeLegacyStoredApiKey();
644
+ }
645
+
646
+ function loadStoredApiKey(endpoint) {
647
+ return readStoredApiKey(endpoint);
648
+ }
649
+
650
+ function storeApiKey(value, endpoint) {
651
+ const trimmed = value.trim();
652
+ if (trimmed) {
653
+ writeStoredApiKey(endpoint, trimmed);
654
+ } else {
655
+ removeStoredApiKey(endpoint);
656
+ }
657
+ }
658
+
659
+ function parseCredential(value) {
660
+ const trimmed = value.trim();
661
+ if (!trimmed) {
662
+ return null;
567
663
  }
664
+
665
+ const authorizationMatch = trimmed.match(
666
+ /^authorization\s*:\s*bearer\s+(.+)$/i
667
+ );
668
+ if (authorizationMatch) {
669
+ return {
670
+ header: "authorization",
671
+ value: `Bearer ${authorizationMatch[1].trim()}`,
672
+ };
673
+ }
674
+
675
+ const bearerMatch = trimmed.match(/^bearer\s+(.+)$/i);
676
+ if (bearerMatch) {
677
+ return {
678
+ header: "authorization",
679
+ value: `Bearer ${bearerMatch[1].trim()}`,
680
+ };
681
+ }
682
+
683
+ const xApiKeyMatch = trimmed.match(/^x-api-key\s*:\s*(.+)$/i);
684
+ if (xApiKeyMatch) {
685
+ return {
686
+ header: "x-api-key",
687
+ value: xApiKeyMatch[1].trim(),
688
+ };
689
+ }
690
+
691
+ return {
692
+ header: "x-api-key",
693
+ value: trimmed,
694
+ };
568
695
  }
569
696
 
570
697
  function buildRequestHeaders() {
571
698
  const headers = {};
572
- const apiKey = apiKeyInput.value.trim();
573
- if (apiKey) {
574
- headers["x-api-key"] = apiKey;
699
+ const credential = parseCredential(apiKeyInput.value);
700
+ if (credential) {
701
+ headers[credential.header] = credential.value;
575
702
  }
576
703
  return headers;
577
704
  }
@@ -2001,7 +2128,8 @@
2001
2128
  */
2002
2129
  function handleFormSubmit(event) {
2003
2130
  event.preventDefault();
2004
- storeApiKey(apiKeyInput.value);
2131
+ syncEndpointCredential();
2132
+ storeApiKey(apiKeyInput.value, endpointUrlInput.value.trim());
2005
2133
  syncUrlState();
2006
2134
  void fetchData();
2007
2135
  }
@@ -2074,6 +2202,18 @@
2074
2202
  render();
2075
2203
  }
2076
2204
 
2205
+ let currentEndpointOrigin = "";
2206
+
2207
+ function syncEndpointCredential() {
2208
+ const endpoint = endpointUrlInput.value.trim();
2209
+ const endpointOrigin = getEndpointOrigin(endpoint);
2210
+ if (endpointOrigin === currentEndpointOrigin) {
2211
+ return;
2212
+ }
2213
+ apiKeyInput.value = loadStoredApiKey(endpoint);
2214
+ currentEndpointOrigin = endpointOrigin;
2215
+ }
2216
+
2077
2217
  /**
2078
2218
  * Initializes the application.
2079
2219
  */
@@ -2081,25 +2221,29 @@
2081
2221
  endpointForm.addEventListener("submit", handleFormSubmit);
2082
2222
  tokenUsagePeriodSelect.addEventListener("change", handlePeriodChange);
2083
2223
  apiKeyInput.addEventListener("input", () => {
2084
- storeApiKey(apiKeyInput.value);
2224
+ storeApiKey(
2225
+ apiKeyInput.value,
2226
+ endpointUrlInput.value.trim()
2227
+ );
2085
2228
  });
2229
+ endpointUrlInput.addEventListener("change", syncEndpointCredential);
2086
2230
  contentArea.addEventListener("click", handleContentAreaClick);
2087
2231
  contentArea.addEventListener("change", handleContentAreaChange);
2088
2232
 
2089
- apiKeyInput.value = loadStoredApiKey();
2090
-
2091
2233
  const urlParams = new URLSearchParams(window.location.search);
2092
2234
  const periodFromUrl = normalizePeriod(urlParams.get("period"));
2093
2235
  const endpointFromUrl = urlParams.get("endpoint");
2236
+ const initialEndpoint = endpointFromUrl || DEFAULT_ENDPOINT;
2094
2237
 
2095
2238
  state.tokenUsagePeriod = periodFromUrl;
2096
2239
  tokenUsagePeriodSelect.value = periodFromUrl;
2240
+ endpointUrlInput.value = initialEndpoint;
2241
+ migrateLegacyApiKey(initialEndpoint);
2242
+ syncEndpointCredential();
2097
2243
 
2098
2244
  if (endpointFromUrl) {
2099
- endpointUrlInput.value = endpointFromUrl;
2100
2245
  void fetchData();
2101
2246
  } else {
2102
- endpointUrlInput.value = DEFAULT_ENDPOINT;
2103
2247
  render();
2104
2248
  }
2105
2249
  }
@@ -1,2 +0,0 @@
1
- import { t as auth } from "./auth-CMjIc9P5.js";
2
- export { auth };