@nick3/copilot-api 1.7.1 → 1.9.15

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.
Files changed (43) hide show
  1. package/README.md +111 -87
  2. package/README.zh-CN.md +111 -84
  3. package/dist/{account-B4EBsn8H.js → account-DjCbqJ2Q.js} +20 -24
  4. package/dist/account-DjCbqJ2Q.js.map +1 -0
  5. package/dist/admin/assets/index-BRnD4-DB.js +110 -0
  6. package/dist/admin/assets/index-CBMFCvqO.css +1 -0
  7. package/dist/admin/index.html +2 -2
  8. package/dist/{auth-xiepl3lk.js → auth--I1utaB6.js} +103 -112
  9. package/dist/auth--I1utaB6.js.map +1 -0
  10. package/dist/{check-usage-3sFXqP0F.js → check-usage-DHvjdha4.js} +6 -7
  11. package/dist/{check-usage-3sFXqP0F.js.map → check-usage-DHvjdha4.js.map} +1 -1
  12. package/dist/{debug-BJfZVBB7.js → debug-BMo6ltbp.js} +6 -6
  13. package/dist/debug-BMo6ltbp.js.map +1 -0
  14. package/dist/{get-copilot-token-COIPGosP.js → get-copilot-token-ZbmbVF0I.js} +9 -5
  15. package/dist/get-copilot-token-ZbmbVF0I.js.map +1 -0
  16. package/dist/main.js +6 -7
  17. package/dist/main.js.map +1 -1
  18. package/dist/{paths-DGlr310R.js → paths-CclKwouX.js} +3 -5
  19. package/dist/{paths-DGlr310R.js.map → paths-CclKwouX.js.map} +1 -1
  20. package/dist/{poll-access-token-c2IYJJHM.js → poll-access-token-CIPDXrcm.js} +9 -21
  21. package/dist/poll-access-token-CIPDXrcm.js.map +1 -0
  22. package/dist/{accounts-manager-Ca9IG0Fv.js → quota-refresh-scheduler-runtime-XD2fDa2K.js} +434 -51
  23. package/dist/quota-refresh-scheduler-runtime-XD2fDa2K.js.map +1 -0
  24. package/dist/{request-outbound-qyTeXbzy.js → request-outbound-CxvpSkOn.js} +14 -9
  25. package/dist/request-outbound-CxvpSkOn.js.map +1 -0
  26. package/dist/request-outbound-Cy6huWjK.js +2 -0
  27. package/dist/{server-DT8b_Z5j.js → server-BDCnb3Ao.js} +1760 -975
  28. package/dist/server-BDCnb3Ao.js.map +1 -0
  29. package/dist/{start-Dayo0B2n.js → start-DkBnp9d8.js} +12 -13
  30. package/dist/start-DkBnp9d8.js.map +1 -0
  31. package/package.json +20 -7
  32. package/dist/account-B4EBsn8H.js.map +0 -1
  33. package/dist/accounts-manager-Ca9IG0Fv.js.map +0 -1
  34. package/dist/admin/assets/index-8eGib92I.js +0 -107
  35. package/dist/admin/assets/index-B2qj1asn.css +0 -1
  36. package/dist/auth-xiepl3lk.js.map +0 -1
  37. package/dist/debug-BJfZVBB7.js.map +0 -1
  38. package/dist/get-copilot-token-COIPGosP.js.map +0 -1
  39. package/dist/poll-access-token-c2IYJJHM.js.map +0 -1
  40. package/dist/request-outbound-DhI9-SrV.js +0 -4
  41. package/dist/request-outbound-qyTeXbzy.js.map +0 -1
  42. package/dist/server-DT8b_Z5j.js.map +0 -1
  43. package/dist/start-Dayo0B2n.js.map +0 -1
@@ -1,12 +1,11 @@
1
- import { O as accountFromState, _ as HTTPError, g as getCopilotUsage, j as consumeOutboundHeadersSnapshot, m as getGitHubUser, x as copilotModelsHeaders, y as copilotBaseUrl } from "./poll-access-token-c2IYJJHM.js";
2
- import { b as getCurrentIdentityEnvironment, c as isAccountEnabled, f as readLegacyToken, h as saveAccountToken, i as ensureAccountClientIdentity, l as listAccountsFromRegistry, o as hasLegacyToken, r as addAccountToRegistry, s as hasRegistry, u as loadAccountToken, v as buildIdentityKey, y as createAccountSessionId } from "./account-B4EBsn8H.js";
3
- import { t as PATHS } from "./paths-DGlr310R.js";
4
- import { t as getCopilotToken } from "./get-copilot-token-COIPGosP.js";
5
- import { c as getAdminDbUserVersion, i as getRequestOutboundStore, o as getAdminDb, s as getAdminDbPath } from "./request-outbound-qyTeXbzy.js";
1
+ import { M as requestContext, O as accountFromState, _ as HTTPError, g as getCopilotUsage, j as consumeOutboundHeadersSnapshot, m as getGitHubUser, x as copilotModelsHeaders, y as copilotBaseUrl } from "./poll-access-token-CIPDXrcm.js";
2
+ import { b as getCurrentIdentityEnvironment, c as isAccountEnabled, f as readLegacyToken, h as saveAccountToken, i as ensureAccountClientIdentity, l as listAccountsFromRegistry, o as hasLegacyToken, r as addAccountToRegistry, s as hasRegistry, u as loadAccountToken, v as buildIdentityKey, y as createAccountSessionId } from "./account-DjCbqJ2Q.js";
3
+ import { t as PATHS } from "./paths-CclKwouX.js";
4
+ import { t as getCopilotToken } from "./get-copilot-token-ZbmbVF0I.js";
5
+ import { c as getAdminDbUserVersion, i as getRequestOutboundStore, o as getAdminDb, s as getAdminDbPath } from "./request-outbound-CxvpSkOn.js";
6
6
  import consola, { consola as consola$1 } from "consola";
7
7
  import fs from "node:fs/promises";
8
8
  import fs$1 from "node:fs";
9
-
10
9
  //#region src/lib/config.ts
11
10
  const PROVIDER_TYPE_ANTHROPIC = "anthropic";
12
11
  const gpt5ExplorationPrompt = `## Exploration and reading files
@@ -15,6 +14,14 @@ const gpt5ExplorationPrompt = `## Exploration and reading files
15
14
  - **multi_tool_use.parallel** Use multi_tool_use.parallel to parallelize tool calls and only this.
16
15
  - **Only make sequential calls if you truly cannot know the next file without seeing a result first.**
17
16
  - **Workflow:** (a) plan all needed reads → (b) issue one parallel batch → (c) analyze results → (d) repeat if new, unpredictable reads arise.`;
17
+ const DEFAULT_QUOTA_REFRESH_CONFIG = {
18
+ enabled: true,
19
+ intervalMinutes: 360,
20
+ startupDelaySeconds: 60,
21
+ staggerMinSeconds: 2,
22
+ staggerMaxSeconds: 5
23
+ };
24
+ const MIN_QUOTA_REFRESH_INTERVAL_MINUTES = 30;
18
25
  const gpt5CommentaryPrompt = `# Working with the user
19
26
 
20
27
  You interact with the user through a terminal. You have 2 ways of communicating with the users:
@@ -69,7 +76,8 @@ const defaultConfig = {
69
76
  capture4xx: false,
70
77
  capture5xx: false,
71
78
  captureOther: false
72
- }
79
+ },
80
+ quotaRefresh: DEFAULT_QUOTA_REFRESH_CONFIG
73
81
  };
74
82
  let cachedConfig = null;
75
83
  function isPlainObject(value) {
@@ -85,6 +93,23 @@ function normalizeNonNegativeNumber(value) {
85
93
  if (value < 0) return void 0;
86
94
  return value;
87
95
  }
96
+ function normalizeQuotaRefreshIntervalMinutes(value) {
97
+ const minutes = normalizeNonNegativeNumber(value) ?? DEFAULT_QUOTA_REFRESH_CONFIG.intervalMinutes;
98
+ if (minutes > 0 && minutes < MIN_QUOTA_REFRESH_INTERVAL_MINUTES) return MIN_QUOTA_REFRESH_INTERVAL_MINUTES;
99
+ return minutes;
100
+ }
101
+ function normalizeQuotaRefreshConfig(value) {
102
+ const raw = isPlainObject(value) ? value : {};
103
+ const staggerMinSeconds = normalizeNonNegativeNumber(raw.staggerMinSeconds) ?? DEFAULT_QUOTA_REFRESH_CONFIG.staggerMinSeconds;
104
+ const rawStaggerMaxSeconds = normalizeNonNegativeNumber(raw.staggerMaxSeconds) ?? DEFAULT_QUOTA_REFRESH_CONFIG.staggerMaxSeconds;
105
+ return {
106
+ enabled: typeof raw.enabled === "boolean" ? raw.enabled : DEFAULT_QUOTA_REFRESH_CONFIG.enabled,
107
+ intervalMinutes: normalizeQuotaRefreshIntervalMinutes(raw.intervalMinutes),
108
+ startupDelaySeconds: normalizeNonNegativeNumber(raw.startupDelaySeconds) ?? DEFAULT_QUOTA_REFRESH_CONFIG.startupDelaySeconds,
109
+ staggerMinSeconds,
110
+ staggerMaxSeconds: Math.max(staggerMinSeconds, rawStaggerMaxSeconds)
111
+ };
112
+ }
88
113
  const LOG_LEVELS = new Set([
89
114
  "error",
90
115
  "warn",
@@ -251,6 +276,20 @@ function mergeDefaultDevMode(config) {
251
276
  changed: true
252
277
  };
253
278
  }
279
+ function mergeDefaultQuotaRefresh(config) {
280
+ const quotaRefresh = normalizeQuotaRefreshConfig(config.quotaRefresh);
281
+ if (JSON.stringify(config.quotaRefresh) === JSON.stringify(quotaRefresh)) return {
282
+ mergedConfig: config,
283
+ changed: false
284
+ };
285
+ return {
286
+ mergedConfig: {
287
+ ...config,
288
+ quotaRefresh
289
+ },
290
+ changed: true
291
+ };
292
+ }
254
293
  function applyConfigMerges(config, mergeFns) {
255
294
  return mergeFns.reduce((acc, mergeFn) => {
256
295
  const result = mergeFn(acc.mergedConfig);
@@ -271,7 +310,8 @@ function mergeConfigWithDefaults() {
271
310
  mergeDefaultModelRefreshInterval,
272
311
  mergeDefaultSessionAffinityRetention,
273
312
  mergeDefaultLogLevel,
274
- mergeDefaultDevMode
313
+ mergeDefaultDevMode,
314
+ mergeDefaultQuotaRefresh
275
315
  ]);
276
316
  if (changed) try {
277
317
  fs$1.writeFileSync(PATHS.CONFIG_PATH, `${JSON.stringify(mergedConfig, null, 2)}\n`, "utf8");
@@ -295,8 +335,8 @@ function normalizeAliasTarget(value) {
295
335
  }
296
336
  function normalizeAliasSpec(value) {
297
337
  if (typeof value === "string") {
298
- const normalizedTarget$1 = normalizeAliasTarget(value);
299
- return normalizedTarget$1 ? { target: normalizedTarget$1 } : null;
338
+ const normalizedTarget = normalizeAliasTarget(value);
339
+ return normalizedTarget ? { target: normalizedTarget } : null;
300
340
  }
301
341
  if (!value || typeof value !== "object") return null;
302
342
  const targetValue = value.target;
@@ -402,6 +442,9 @@ function getModelRefreshIntervalMs() {
402
442
  if (!Number.isFinite(hours) || hours <= 0) return 0;
403
443
  return hours * 60 * 60 * 1e3;
404
444
  }
445
+ function getQuotaRefreshConfig() {
446
+ return normalizeQuotaRefreshConfig(getConfig().quotaRefresh);
447
+ }
405
448
  function getSessionAffinityRetentionDays() {
406
449
  return normalizeNonNegativeNumber(getConfig().sessionAffinityRetentionDays) ?? defaultConfig.sessionAffinityRetentionDays ?? 7;
407
450
  }
@@ -435,11 +478,15 @@ function isForceAgentEnabled() {
435
478
  function normalizeProviderBaseUrl(url) {
436
479
  return url.trim().replace(/\/+$/u, "");
437
480
  }
438
- function resolveProviderAuthType(providerName, authType) {
439
- if (authType === void 0 || authType === "x-api-key") return "x-api-key";
481
+ function getDefaultProviderAuthType(providerType) {
482
+ return providerType === "openai-compatible" ? "authorization" : "x-api-key";
483
+ }
484
+ function resolveProviderAuthType(providerName, authType, providerType) {
485
+ if (authType === void 0) return getDefaultProviderAuthType(providerType);
486
+ if (authType === "x-api-key") return "x-api-key";
440
487
  if (authType === "authorization") return authType;
441
- consola.warn(`Provider ${providerName} has invalid authType '${authType}', ignoring provider`);
442
- return null;
488
+ consola.warn(`Provider ${providerName} has invalid authType '${authType}', falling back to ${getDefaultProviderAuthType(providerType)}`);
489
+ return getDefaultProviderAuthType(providerType);
443
490
  }
444
491
  function getProviderConfig(name) {
445
492
  const providerName = name.trim();
@@ -447,21 +494,21 @@ function getProviderConfig(name) {
447
494
  const provider = getConfig().providers?.[providerName];
448
495
  if (!provider) return null;
449
496
  if (provider.enabled === false) return null;
450
- if ((provider.type ?? PROVIDER_TYPE_ANTHROPIC) !== PROVIDER_TYPE_ANTHROPIC) {
451
- consola.warn(`Provider ${providerName} is ignored because only anthropic type is supported`);
497
+ const type = provider.type ?? "anthropic";
498
+ if (type !== "anthropic" && type !== "openai-compatible") {
499
+ consola.warn(`Provider ${providerName} is ignored because type '${type}' is not supported`);
452
500
  return null;
453
501
  }
454
502
  const baseUrl = normalizeProviderBaseUrl(provider.baseUrl ?? "");
455
503
  const apiKey = (provider.apiKey ?? "").trim();
456
- const authType = resolveProviderAuthType(providerName, provider.authType);
457
- if (!authType) return null;
504
+ const authType = resolveProviderAuthType(providerName, provider.authType, type);
458
505
  if (!baseUrl || !apiKey) {
459
506
  consola.warn(`Provider ${providerName} is enabled but missing baseUrl or apiKey`);
460
507
  return null;
461
508
  }
462
509
  return {
463
510
  name: providerName,
464
- type: PROVIDER_TYPE_ANTHROPIC,
511
+ type,
465
512
  baseUrl,
466
513
  apiKey,
467
514
  authType,
@@ -481,7 +528,6 @@ function isResponsesApiWebSearchEnabled() {
481
528
  function getClaudeTokenMultiplier() {
482
529
  return getConfig().claudeTokenMultiplier ?? 1.15;
483
530
  }
484
-
485
531
  //#endregion
486
532
  //#region src/lib/account-affinity.ts
487
533
  const DEFAULT_MAX_ENTRIES$1 = 1e4;
@@ -514,6 +560,31 @@ var AccountAffinityCache = class {
514
560
  this.setMemory(key, accountId);
515
561
  return accountId;
516
562
  }
563
+ getMany(keys) {
564
+ const result = /* @__PURE__ */ new Map();
565
+ const missingKeys = new Array();
566
+ const now = Date.now();
567
+ for (const key of keys) {
568
+ if (result.has(key)) continue;
569
+ const entry = this.cache.get(key);
570
+ if (!entry) {
571
+ missingKeys.push(key);
572
+ continue;
573
+ }
574
+ if (now >= entry.expiresAt) {
575
+ this.cache.delete(key);
576
+ missingKeys.push(key);
577
+ continue;
578
+ }
579
+ result.set(key, entry.accountId);
580
+ }
581
+ if (missingKeys.length === 0) return result;
582
+ const persistentEntries = this.readPersistentEntries(missingKeys);
583
+ for (const [key, accountId] of persistentEntries) this.setMemory(key, accountId);
584
+ if (result.size === 0) return persistentEntries;
585
+ for (const [key, accountId] of persistentEntries) result.set(key, accountId);
586
+ return result;
587
+ }
517
588
  /** Record a successful account mapping. Refreshes TTL and moves the entry to the newest position. */
518
589
  set(key, accountId) {
519
590
  this.setMemory(key, accountId);
@@ -538,7 +609,7 @@ var AccountAffinityCache = class {
538
609
  get size() {
539
610
  return this.cache.size;
540
611
  }
541
- getPersistentStore() {
612
+ getPersistentStore(options = {}) {
542
613
  if (this.persistentStore) return this.persistentStore;
543
614
  if (!this.persistentStoreProvider) return;
544
615
  try {
@@ -546,7 +617,10 @@ var AccountAffinityCache = class {
546
617
  if (store) this.persistentStore = store;
547
618
  return store;
548
619
  } catch (error) {
549
- this.persistentStoreProvider = void 0;
620
+ if (options.throwOnProviderFailure) {
621
+ consola.error("Failed to resolve affinity persistence store:", error);
622
+ throw new Error("Affinity persistence store provider failed");
623
+ }
550
624
  consola.warn("Failed to resolve affinity persistence store:", error);
551
625
  return;
552
626
  }
@@ -561,6 +635,27 @@ var AccountAffinityCache = class {
561
635
  return;
562
636
  }
563
637
  }
638
+ readPersistentEntries(keys) {
639
+ const store = this.getPersistentStore({ throwOnProviderFailure: true });
640
+ if (!store) return /* @__PURE__ */ new Map();
641
+ if (store.getMany) try {
642
+ return store.getMany(keys);
643
+ } catch (error) {
644
+ consola.warn("Failed to batch-read affinity mappings from persistent store:", error);
645
+ throw new Error(`Affinity persistent store batch lookup failed for ${keys.length} keys`);
646
+ }
647
+ const result = /* @__PURE__ */ new Map();
648
+ const failedKeys = new Array();
649
+ for (const key of keys) try {
650
+ const accountId = store.get(key);
651
+ if (accountId) result.set(key, accountId);
652
+ } catch (error) {
653
+ failedKeys.push(key);
654
+ consola.warn("Failed to read affinity mapping from persistent store:", error);
655
+ }
656
+ if (failedKeys.length > 0) throw new Error(`Affinity persistent store lookup failed for ${failedKeys.length}/${keys.length} keys`);
657
+ return result;
658
+ }
564
659
  writePersistentEntry(key, accountId) {
565
660
  const store = this.getPersistentStore();
566
661
  if (!store) return;
@@ -627,7 +722,6 @@ function isAffinityAccountUsable(accountId, accounts) {
627
722
  if (account.failed) return void 0;
628
723
  return account;
629
724
  }
630
-
631
725
  //#endregion
632
726
  //#region src/lib/dev-mode.ts
633
727
  function isDevModeEnabled() {
@@ -642,7 +736,6 @@ function isCapture5xxEnabled() {
642
736
  function isCaptureOtherEnabled() {
643
737
  return getConfig().devMode?.captureOther === true;
644
738
  }
645
-
646
739
  //#endregion
647
740
  //#region src/services/copilot/copilot-fetch.ts
648
741
  const pendingCaptures = /* @__PURE__ */ new Map();
@@ -669,14 +762,14 @@ async function flushPendingCapture(requestId) {
669
762
  function snapshotHeaders(init) {
670
763
  const headers = init.headers;
671
764
  if (headers instanceof Headers) {
672
- const out$1 = {};
673
- for (const [key, value] of headers.entries()) out$1[key] = value;
674
- return out$1;
765
+ const out = {};
766
+ for (const [key, value] of headers.entries()) out[key] = value;
767
+ return out;
675
768
  }
676
769
  if (Array.isArray(headers)) {
677
- const out$1 = {};
678
- for (const [key, value] of headers) out$1[key] = value;
679
- return out$1;
770
+ const out = {};
771
+ for (const [key, value] of headers) out[key] = value;
772
+ return out;
680
773
  }
681
774
  const out = {};
682
775
  for (const [key, value] of Object.entries(headers ?? {})) out[key] = value;
@@ -725,7 +818,7 @@ async function copilotFetch(input, init, ctx) {
725
818
  headers: snapshotHeaders(init),
726
819
  ...snapshotBody(init)
727
820
  };
728
- const response = await fetch(input, init);
821
+ const response = await (ctx.fetchImpl ?? requestContext.getStore()?.fetchImpl ?? fetch)(input, init);
729
822
  if (!(ctx.requestId !== void 0 && ctx.capturable !== false && shouldCaptureStatus(response.status))) return response;
730
823
  const contentType = response.headers.get("content-type") ?? "";
731
824
  const isSSE = contentType.includes("text/event-stream");
@@ -801,7 +894,6 @@ function persistNow({ requestSnapshot, status, responseBody, responseBodyKind, r
801
894
  consola.debug("copilotFetch persist failed", error);
802
895
  }
803
896
  }
804
-
805
897
  //#endregion
806
898
  //#region src/services/copilot/get-models.ts
807
899
  const getModels = async (account, options) => {
@@ -826,7 +918,6 @@ const getModels = async (account, options) => {
826
918
  } catch {}
827
919
  return models;
828
920
  };
829
-
830
921
  //#endregion
831
922
  //#region src/lib/accounts-manager-auth.ts
832
923
  const takeAuthSnapshot = (account) => ({
@@ -895,7 +986,6 @@ const applyUnauthorizedIfCurrent = (account, snapshot, reason) => {
895
986
  setAccountFailedState(account, reason);
896
987
  return true;
897
988
  };
898
-
899
989
  //#endregion
900
990
  //#region src/lib/accounts-manager-quota.ts
901
991
  const getCostUnits = (model) => {
@@ -930,10 +1020,6 @@ const releasePremiumReservation = (account, reservation) => {
930
1020
  account.premiumReserved = Math.max(0, nextReserved);
931
1021
  if (reservations.size === 0) account.premiumReservations = void 0;
932
1022
  };
933
-
934
- //#endregion
935
- //#region src/lib/stats-store.ts
936
- const DEFAULT_STATS_RETENTION_DAYS = 180;
937
1023
  function toLocalDateString(ms) {
938
1024
  const d = new Date(ms);
939
1025
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
@@ -1082,7 +1168,7 @@ var StatsStore = class {
1082
1168
  });
1083
1169
  return this.mergeMetricsAndConsumption(dailyMetrics, byAccountMetrics, consumption);
1084
1170
  }
1085
- cleanupStatsRetention(retentionDays = DEFAULT_STATS_RETENTION_DAYS) {
1171
+ cleanupStatsRetention(retentionDays = 180) {
1086
1172
  try {
1087
1173
  const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
1088
1174
  const cutoffDate = toLocalDateString(cutoffMs);
@@ -1151,7 +1237,6 @@ var StatsStore = class {
1151
1237
  };
1152
1238
  }
1153
1239
  };
1154
-
1155
1240
  //#endregion
1156
1241
  //#region src/lib/request-history.ts
1157
1242
  const DEFAULT_RETENTION_DAYS = 35;
@@ -1299,6 +1384,8 @@ function buildInsertArgs(record) {
1299
1384
  toDbNull(record.affinityKeyUsed),
1300
1385
  toDbNull(record.affinityKeySource),
1301
1386
  toDbNull(record.selectionReason),
1387
+ toDbNull(record.responsesItemOwnerLookupKeysJson),
1388
+ toDbNull(record.responsesItemOwnerRecordedKeysJson),
1302
1389
  toDbNull(record.tokensInput),
1303
1390
  toDbNull(record.tokensOutput),
1304
1391
  toDbNull(record.tokensTotal),
@@ -1359,6 +1446,8 @@ var RequestHistoryStore = class {
1359
1446
  "affinity_key_used",
1360
1447
  "affinity_key_source",
1361
1448
  "selection_reason",
1449
+ "responses_item_owner_lookup_keys_json",
1450
+ "responses_item_owner_recorded_keys_json",
1362
1451
  "tokens_input",
1363
1452
  "tokens_output",
1364
1453
  "tokens_total",
@@ -1535,7 +1624,7 @@ var RequestHistoryStore = class {
1535
1624
  } catch (error) {
1536
1625
  consola.debug("Failed to cleanup request_log retention", error);
1537
1626
  }
1538
- import("./request-outbound-DhI9-SrV.js").then((m) => m.getRequestOutboundStore().cleanupOrphans()).catch(() => {});
1627
+ import("./request-outbound-Cy6huWjK.js").then((m) => m.getRequestOutboundStore().cleanupOrphans()).catch(() => {});
1539
1628
  }
1540
1629
  meta() {
1541
1630
  return {
@@ -1627,15 +1716,18 @@ function extractResponsesUsageFromResult(result) {
1627
1716
  function getStatsStore() {
1628
1717
  return getStatsStoreInstance();
1629
1718
  }
1630
-
1631
1719
  //#endregion
1632
1720
  //#region src/lib/session-affinity-store.ts
1633
1721
  const DAY_MS = 1440 * 60 * 1e3;
1634
1722
  const DEFAULT_MAX_AGE_MS = 7 * DAY_MS;
1635
1723
  const CLEANUP_INTERVAL_MS = DAY_MS;
1724
+ const MAX_BATCH_KEYS = 500;
1636
1725
  const maybeUnref = (timer) => {
1637
1726
  timer.unref();
1638
1727
  };
1728
+ function* chunks(values, size) {
1729
+ for (let index = 0; index < values.length; index += size) yield values.slice(index, index + size);
1730
+ }
1639
1731
  var SessionAffinityStore = class {
1640
1732
  db;
1641
1733
  constructor(db) {
@@ -1657,6 +1749,65 @@ var SessionAffinityStore = class {
1657
1749
  }
1658
1750
  return row.account_id;
1659
1751
  }
1752
+ getMany(cacheKeys) {
1753
+ const uniqueKeys = [...new Set(cacheKeys)].filter(Boolean);
1754
+ const result = /* @__PURE__ */ new Map();
1755
+ let chunkIndex = 0;
1756
+ for (const keys of chunks(uniqueKeys, MAX_BATCH_KEYS)) {
1757
+ try {
1758
+ this.readManyChunk(keys, result);
1759
+ } catch (error) {
1760
+ consola.error("Failed to batch-read session affinity mappings", {
1761
+ chunkIndex,
1762
+ chunkKeyCount: keys.length,
1763
+ error,
1764
+ totalKeyCount: uniqueKeys.length
1765
+ });
1766
+ throw error;
1767
+ }
1768
+ chunkIndex += 1;
1769
+ }
1770
+ if (result.size === 0) return result;
1771
+ const now = Date.now();
1772
+ let touchChunkIndex = 0;
1773
+ const touchedKeyCount = result.size;
1774
+ for (const keys of chunks([...result.keys()], MAX_BATCH_KEYS)) {
1775
+ try {
1776
+ this.touchManyChunk(keys, now);
1777
+ } catch (error) {
1778
+ consola.warn("Failed to batch-update session affinity last_used_at_ms", {
1779
+ chunkIndex: touchChunkIndex,
1780
+ chunkKeyCount: keys.length,
1781
+ error,
1782
+ touchedKeyCount
1783
+ });
1784
+ }
1785
+ touchChunkIndex += 1;
1786
+ }
1787
+ return result;
1788
+ }
1789
+ readManyChunk(cacheKeys, result) {
1790
+ if (cacheKeys.length === 0) return;
1791
+ const placeholders = cacheKeys.map(() => "?").join(", ");
1792
+ const rows = this.db.query(`SELECT cache_key, account_id FROM session_affinity
1793
+ WHERE cache_key IN (${placeholders});`).all(...cacheKeys);
1794
+ for (const row of rows) {
1795
+ if (!row.cache_key || !row.account_id) {
1796
+ consola.error("Invalid session affinity row returned from database", {
1797
+ hasAccountId: Boolean(row.account_id),
1798
+ hasCacheKey: Boolean(row.cache_key)
1799
+ });
1800
+ throw new Error("Invalid session affinity row returned from database");
1801
+ }
1802
+ result.set(row.cache_key, row.account_id);
1803
+ }
1804
+ }
1805
+ touchManyChunk(cacheKeys, now) {
1806
+ if (cacheKeys.length === 0) return;
1807
+ const placeholders = cacheKeys.map(() => "?").join(", ");
1808
+ this.db.query(`UPDATE session_affinity SET last_used_at_ms = ?
1809
+ WHERE cache_key IN (${placeholders});`).run(now, ...cacheKeys);
1810
+ }
1660
1811
  set(cacheKey, accountId) {
1661
1812
  const now = Date.now();
1662
1813
  try {
@@ -1724,7 +1875,6 @@ function applySharedSessionAffinityRetention(retentionMs = getSessionAffinityRet
1724
1875
  consola.warn("Failed to apply session affinity retention:", error);
1725
1876
  }
1726
1877
  }
1727
-
1728
1878
  //#endregion
1729
1879
  //#region src/lib/session-ownership.ts
1730
1880
  const DEFAULT_MAX_ENTRIES = 1e4;
@@ -1770,7 +1920,6 @@ var SessionOwnershipCache = class {
1770
1920
  this.cache.clear();
1771
1921
  }
1772
1922
  };
1773
-
1774
1923
  //#endregion
1775
1924
  //#region src/lib/accounts-manager.ts
1776
1925
  /** Quota cache TTL in milliseconds (45 seconds) for pre-request selection. */
@@ -1798,6 +1947,10 @@ function getInitialSelectionReason(cacheKey, rotationStart) {
1798
1947
  function preserveSubagentSelectionReason(initialSelectionReason, nextSelectionReason) {
1799
1948
  return initialSelectionReason.startsWith("subagent_") ? initialSelectionReason : nextSelectionReason;
1800
1949
  }
1950
+ function normalizeCacheKeys(keys) {
1951
+ if (!keys) return [];
1952
+ return [...new Set(keys.map((key) => key.trim()).filter(Boolean))];
1953
+ }
1801
1954
  /** Manages multiple GitHub Copilot accounts at runtime. */
1802
1955
  var AccountsManager = class {
1803
1956
  accounts = /* @__PURE__ */ new Map();
@@ -1873,6 +2026,12 @@ var AccountsManager = class {
1873
2026
  this.modelsRefreshIntervalMs = Number.isFinite(intervalMs) && intervalMs > 0 ? intervalMs : 0;
1874
2027
  this.scheduleModelsRefresh();
1875
2028
  }
2029
+ getQuotaRefreshAccounts() {
2030
+ return this.getOrderedEnabledAccounts().filter((account) => !this.isAccountFailed(account) && account.copilotToken !== void 0);
2031
+ }
2032
+ refreshAccountQuota(account) {
2033
+ return this.refreshQuota(account);
2034
+ }
1876
2035
  computeTokenRefreshDelayMs(refreshInSeconds) {
1877
2036
  const baseDelay = Math.max((refreshInSeconds - 60) * 1e3, 1e3);
1878
2037
  const jitter = Math.floor(Math.random() * TOKEN_REFRESH_JITTER_MS);
@@ -2309,6 +2468,15 @@ var AccountsManager = class {
2309
2468
  async selectAccountForRequest(candidates, context) {
2310
2469
  if (candidates.length === 0) throw new Error("selectAccountForRequest requires at least one candidate");
2311
2470
  const orderedAccounts = this.getOrderedEnabledAccounts();
2471
+ const itemOwnerSelection = await this.selectPreferredResponsesItemOwner({
2472
+ ownershipKeys: context?.responsesItemOwnershipKeys,
2473
+ orderedAccounts,
2474
+ candidates
2475
+ });
2476
+ if (itemOwnerSelection.result) {
2477
+ this.attachConfirmOwnership(itemOwnerSelection.result, context?.ownershipWriteSessionId);
2478
+ return itemOwnerSelection.result;
2479
+ }
2312
2480
  const ownerSelection = await this.selectPreferredSessionOwner({
2313
2481
  lookupSessionId: context?.ownershipLookupSessionId,
2314
2482
  orderedAccounts,
@@ -2389,6 +2557,12 @@ var AccountsManager = class {
2389
2557
  if (!normalizedCacheKey) return;
2390
2558
  this.affinityCache.delete(normalizedCacheKey);
2391
2559
  }
2560
+ recordResponsesItemOwnership(ownershipKeys, accountId) {
2561
+ if (!this.accountAffinityEnabled) return;
2562
+ const normalizedAccountId = accountId.trim();
2563
+ if (!normalizedAccountId) return;
2564
+ for (const key of normalizeCacheKeys(ownershipKeys)) this.affinityCache.set(key, normalizedAccountId);
2565
+ }
2392
2566
  attachConfirmOwnership(result, ownershipWriteSessionId) {
2393
2567
  const rootSessionId = ownershipWriteSessionId?.trim();
2394
2568
  if (!rootSessionId) return;
@@ -2398,6 +2572,52 @@ var AccountsManager = class {
2398
2572
  this.sessionOwnership.set(rootSessionId, result.account.id);
2399
2573
  };
2400
2574
  }
2575
+ async selectPreferredResponsesItemOwner(params) {
2576
+ if (!this.accountAffinityEnabled) return {};
2577
+ const { orderedAccounts, candidates } = params;
2578
+ const ownershipKeys = normalizeCacheKeys(params.ownershipKeys);
2579
+ if (ownershipKeys.length === 0) return {};
2580
+ let ownershipMappings;
2581
+ try {
2582
+ ownershipMappings = this.affinityCache.getMany(ownershipKeys);
2583
+ } catch (error) {
2584
+ consola.warn("Failed to read Responses item owner mappings; falling back", {
2585
+ error,
2586
+ ownershipKeyCount: ownershipKeys.length
2587
+ });
2588
+ return {};
2589
+ }
2590
+ const accountIds = /* @__PURE__ */ new Map();
2591
+ for (const key of ownershipKeys) {
2592
+ const accountId = ownershipMappings.get(key);
2593
+ if (!accountId) continue;
2594
+ accountIds.set(accountId, key);
2595
+ if (accountIds.size > 1) {
2596
+ consola.warn("Conflicting Responses item owner mappings; falling back", {
2597
+ accountIds: [...accountIds.keys()],
2598
+ ownershipKeyCount: ownershipKeys.length
2599
+ });
2600
+ return {};
2601
+ }
2602
+ }
2603
+ if (accountIds.size === 0) return {};
2604
+ const [[accountId, ownerKey]] = [...accountIds];
2605
+ const ownerResult = await this.tryAffinityAccount(accountId, orderedAccounts, candidates);
2606
+ if (!ownerResult) {
2607
+ if (this.isMissingOrDisabledAccount(accountId, orderedAccounts)) this.deleteResponsesItemOwnershipMappings(ownershipKeys, accountId);
2608
+ return {};
2609
+ }
2610
+ ownerResult.affinityHit = true;
2611
+ ownerResult.affinityCacheKey = ownerKey;
2612
+ ownerResult.selectionReason = "responses_item_owner_hit";
2613
+ return { result: ownerResult };
2614
+ }
2615
+ isMissingOrDisabledAccount(accountId, orderedAccounts) {
2616
+ return !orderedAccounts.some((account) => account.id === accountId);
2617
+ }
2618
+ deleteResponsesItemOwnershipMappings(ownershipKeys, accountId) {
2619
+ for (const key of ownershipKeys) if (this.affinityCache.get(key) === accountId) this.affinityCache.delete(key);
2620
+ }
2401
2621
  async selectPreferredSessionOwner(params) {
2402
2622
  const { lookupSessionId, orderedAccounts, candidates } = params;
2403
2623
  if (lookupSessionId === void 0) return {};
@@ -2463,13 +2683,13 @@ var AccountsManager = class {
2463
2683
  if (scoredAccounts.length > 0) {
2464
2684
  const scoredAccountsInSelectionOrder = this.shuffleWithinEqualRemainingBuckets(scoredAccounts).map(({ account }) => account);
2465
2685
  const scoredAccountSet = new Set(scoredAccountsInSelectionOrder);
2466
- const unknownQuotaAccounts$1 = orderedAccounts.filter((account) => !scoredAccountSet.has(account) && !account.unlimited);
2467
- const unlimitedAccounts$1 = orderedAccounts.filter((account) => !scoredAccountSet.has(account) && account.unlimited);
2686
+ const unknownQuotaAccounts = orderedAccounts.filter((account) => !scoredAccountSet.has(account) && !account.unlimited);
2687
+ const unlimitedAccounts = orderedAccounts.filter((account) => !scoredAccountSet.has(account) && account.unlimited);
2468
2688
  return {
2469
2689
  accounts: [
2470
2690
  ...scoredAccountsInSelectionOrder,
2471
- ...unknownQuotaAccounts$1,
2472
- ...unlimitedAccounts$1
2691
+ ...unknownQuotaAccounts,
2692
+ ...unlimitedAccounts
2473
2693
  ],
2474
2694
  premiumRemainingOrderedAccountIds: new Set(scoredAccountsInSelectionOrder.map((account) => account.id))
2475
2695
  };
@@ -2868,7 +3088,170 @@ var AccountsManager = class {
2868
3088
  };
2869
3089
  /** Singleton instance of AccountsManager */
2870
3090
  const accountsManager = new AccountsManager({ persistentAffinityStore: getSharedSessionAffinityStore });
2871
-
2872
3091
  //#endregion
2873
- export { isMessageStartInputTokensFallbackEnabled as A, getModelAliasesInfo as C, getSmallModel as D, getReasoningEffortForModel as E, resolveModelAlias as F, shouldCompactUseSmallModel as I, isResponsesApiContextManagementModel as M, isResponsesApiWebSearchEnabled as N, isAccountAffinityEnabled as O, mergeConfigWithDefaults as P, getModelAliases as S, getProviderConfig as T, getAnthropicApiKey as _, getClientIpInfo as a, getExtraPromptForModel as b, normalizeChatCompletionsUsage as c, toLocalDateString as d, copilotFetch as f, getAliasTargetSet as g, PROVIDER_TYPE_ANTHROPIC as h, extractResponsesUsageFromStreamEvent as i, isMessagesApiEnabled as j, isForceAgentEnabled as k, normalizeEmbeddingsUsage as l, isDevModeEnabled as m, applySharedSessionAffinityRetention as n, getRequestHistoryStore as o, flushPendingCapture as p, extractResponsesUsageFromResult as r, getStatsStore as s, accountsManager as t, normalizeMessagesUsage as u, getClaudeTokenMultiplier as v, getModelRefreshIntervalMs as w, getLogLevel as x, getConfig as y };
2874
- //# sourceMappingURL=accounts-manager-Ca9IG0Fv.js.map
3092
+ //#region src/lib/accounts-manager-quota-scheduler.ts
3093
+ const QUOTA_REFRESH_FRESHNESS_GUARD_MS = 300 * 1e3;
3094
+ const QUOTA_REFRESH_INTERVAL_JITTER_RATIO = .1;
3095
+ const defaultTimer = {
3096
+ setTimer(callback, delayMs) {
3097
+ return setTimeout(callback, delayMs);
3098
+ },
3099
+ clearTimer(handle) {
3100
+ clearTimeout(handle);
3101
+ }
3102
+ };
3103
+ function secondsToMs(seconds) {
3104
+ return seconds * 1e3;
3105
+ }
3106
+ function minutesToMs(minutes) {
3107
+ return minutes * 60 * 1e3;
3108
+ }
3109
+ function isSchedulingEnabled(config) {
3110
+ return config.enabled && config.intervalMinutes > 0;
3111
+ }
3112
+ var QuotaRefreshScheduler = class {
3113
+ config;
3114
+ logger;
3115
+ manager;
3116
+ now;
3117
+ random;
3118
+ timer;
3119
+ generation = 0;
3120
+ pendingDelay;
3121
+ roundTimer;
3122
+ stopped = true;
3123
+ constructor(options) {
3124
+ this.config = options.config;
3125
+ this.logger = options.logger ?? consola;
3126
+ this.manager = options.manager;
3127
+ this.now = options.now ?? Date.now;
3128
+ this.random = options.random ?? Math.random;
3129
+ this.timer = options.timer ?? defaultTimer;
3130
+ }
3131
+ start(config = this.config) {
3132
+ this.config = config;
3133
+ this.stop();
3134
+ if (!isSchedulingEnabled(this.config)) return;
3135
+ this.stopped = false;
3136
+ this.scheduleRound(secondsToMs(this.config.startupDelaySeconds), this.generation);
3137
+ }
3138
+ stop() {
3139
+ this.generation += 1;
3140
+ this.stopped = true;
3141
+ this.clearRoundTimer();
3142
+ this.clearPendingDelay();
3143
+ }
3144
+ updateConfig(config) {
3145
+ this.start(config);
3146
+ }
3147
+ isActiveGeneration(generation) {
3148
+ return !this.stopped && this.generation === generation;
3149
+ }
3150
+ scheduleRound(delayMs, generation) {
3151
+ this.clearRoundTimer();
3152
+ const handle = this.timer.setTimer(() => {
3153
+ if (this.roundTimer === handle) this.roundTimer = void 0;
3154
+ if (this.isActiveGeneration(generation)) this.runRound(generation);
3155
+ }, Math.max(0, delayMs));
3156
+ this.roundTimer = handle;
3157
+ }
3158
+ scheduleNextRound(generation) {
3159
+ if (!this.isActiveGeneration(generation) || !isSchedulingEnabled(this.config)) return;
3160
+ const intervalMs = minutesToMs(this.config.intervalMinutes);
3161
+ this.scheduleRound(this.withJitter(intervalMs), generation);
3162
+ }
3163
+ withJitter(intervalMs) {
3164
+ const jitterMs = Math.floor(intervalMs * QUOTA_REFRESH_INTERVAL_JITTER_RATIO * (this.random() * 2 - 1));
3165
+ return Math.max(0, intervalMs + jitterMs);
3166
+ }
3167
+ getStaggerDelayMs() {
3168
+ const minMs = secondsToMs(this.config.staggerMinSeconds);
3169
+ const maxMs = secondsToMs(this.config.staggerMaxSeconds);
3170
+ if (maxMs <= minMs) return minMs;
3171
+ return minMs + Math.floor(this.random() * (maxMs - minMs));
3172
+ }
3173
+ isFresh(account) {
3174
+ if (account.lastQuotaFetch === void 0) return false;
3175
+ return this.now() - account.lastQuotaFetch < QUOTA_REFRESH_FRESHNESS_GUARD_MS;
3176
+ }
3177
+ async schedulerDelay(delayMs) {
3178
+ this.clearPendingDelay();
3179
+ await new Promise((resolve) => {
3180
+ const handle = this.timer.setTimer(() => {
3181
+ if (this.pendingDelay?.handle === handle) this.pendingDelay = void 0;
3182
+ resolve();
3183
+ }, Math.max(0, delayMs));
3184
+ this.pendingDelay = {
3185
+ handle,
3186
+ resolve
3187
+ };
3188
+ });
3189
+ }
3190
+ clearRoundTimer() {
3191
+ if (this.roundTimer === void 0) return;
3192
+ this.timer.clearTimer(this.roundTimer);
3193
+ this.roundTimer = void 0;
3194
+ }
3195
+ clearPendingDelay() {
3196
+ if (!this.pendingDelay) return;
3197
+ const { handle, resolve } = this.pendingDelay;
3198
+ this.pendingDelay = void 0;
3199
+ this.timer.clearTimer(handle);
3200
+ resolve();
3201
+ }
3202
+ async runRound(generation) {
3203
+ try {
3204
+ const accounts = this.manager.getQuotaRefreshAccounts();
3205
+ for (const [index, account] of accounts.entries()) {
3206
+ if (!this.isActiveGeneration(generation)) return;
3207
+ if (this.isFresh(account)) continue;
3208
+ try {
3209
+ await this.manager.refreshAccountQuota(account);
3210
+ } catch (error) {
3211
+ if (this.isActiveGeneration(generation)) this.logger.debug("Background quota refresh failed", {
3212
+ accountId: account.id,
3213
+ error
3214
+ });
3215
+ }
3216
+ if (!this.isActiveGeneration(generation)) return;
3217
+ if (index < accounts.length - 1) {
3218
+ await this.schedulerDelay(this.getStaggerDelayMs());
3219
+ if (!this.isActiveGeneration(generation)) return;
3220
+ }
3221
+ }
3222
+ } catch (error) {
3223
+ if (this.isActiveGeneration(generation)) this.logger.error("Background quota refresh round failed", error);
3224
+ } finally {
3225
+ this.scheduleNextRound(generation);
3226
+ }
3227
+ }
3228
+ };
3229
+ //#endregion
3230
+ //#region src/lib/quota-refresh-scheduler-runtime.ts
3231
+ const quotaRefreshScheduler = new QuotaRefreshScheduler({
3232
+ config: getQuotaRefreshConfig(),
3233
+ manager: accountsManager
3234
+ });
3235
+ const registeredShutdownRuntimes = /* @__PURE__ */ new WeakSet();
3236
+ let isQuotaRefreshSchedulerStarted = false;
3237
+ function startQuotaRefreshSchedulerFromConfig() {
3238
+ isQuotaRefreshSchedulerStarted = true;
3239
+ quotaRefreshScheduler.start(getQuotaRefreshConfig());
3240
+ }
3241
+ function stopQuotaRefreshScheduler() {
3242
+ isQuotaRefreshSchedulerStarted = false;
3243
+ quotaRefreshScheduler.stop();
3244
+ }
3245
+ function registerQuotaRefreshSchedulerShutdownCleanup(runtime = process) {
3246
+ if (registeredShutdownRuntimes.has(runtime)) return;
3247
+ registeredShutdownRuntimes.add(runtime);
3248
+ runtime.once("exit", () => stopQuotaRefreshScheduler());
3249
+ }
3250
+ function updateQuotaRefreshSchedulerFromConfig() {
3251
+ if (!isQuotaRefreshSchedulerStarted) return;
3252
+ quotaRefreshScheduler.updateConfig(getQuotaRefreshConfig());
3253
+ }
3254
+ //#endregion
3255
+ export { getReasoningEffortForModel as A, shouldCompactUseSmallModel as B, getConfig as C, getModelAliasesInfo as D, getModelAliases as E, isMessagesApiEnabled as F, isResponsesApiContextManagementModel as I, isResponsesApiWebSearchEnabled as L, isAccountAffinityEnabled as M, isForceAgentEnabled as N, getModelRefreshIntervalMs as O, isMessageStartInputTokensFallbackEnabled as P, mergeConfigWithDefaults as R, getClaudeTokenMultiplier as S, getLogLevel as T, flushPendingCapture as _, accountsManager as a, getAliasTargetSet as b, extractResponsesUsageFromStreamEvent as c, getStatsStore as d, normalizeChatCompletionsUsage as f, copilotFetch as g, toLocalDateString as h, updateQuotaRefreshSchedulerFromConfig as i, getSmallModel as j, getProviderConfig as k, getClientIpInfo as l, normalizeMessagesUsage as m, startQuotaRefreshSchedulerFromConfig as n, applySharedSessionAffinityRetention as o, normalizeEmbeddingsUsage as p, stopQuotaRefreshScheduler as r, extractResponsesUsageFromResult as s, registerQuotaRefreshSchedulerShutdownCleanup as t, getRequestHistoryStore as u, isDevModeEnabled as v, getExtraPromptForModel as w, getAnthropicApiKey as x, PROVIDER_TYPE_ANTHROPIC as y, resolveModelAlias as z };
3256
+
3257
+ //# sourceMappingURL=quota-refresh-scheduler-runtime-XD2fDa2K.js.map