@albinocrabs/o-switcher 0.2.0 → 0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Multi-profile auth rotation for OpenAI accounts
8
+ - Watch all `auth-work*.json` files, not just `auth.json`
9
+ - Add `switchProfile` / `switchToNextProfile` for credential rotation
10
+ - Auto-switch to next healthy profile when health drops below 30%
11
+
3
12
  ## 0.2.0
4
13
 
5
14
  ### Minor Changes
@@ -1502,6 +1502,8 @@ var nextProfileId = (store, provider) => {
1502
1502
  return `${provider}-${maxN + 1}`;
1503
1503
  };
1504
1504
  var AUTH_JSON_PATH = path.join(os.homedir(), ".local", "share", "opencode", "auth.json");
1505
+ var AUTH_DIR = path.join(os.homedir(), ".local", "share", "opencode");
1506
+ var isAuthFile = (filename) => filename === "auth.json" || /^auth-work\d+\.json$/.test(filename);
1505
1507
  var DEBOUNCE_MS = 100;
1506
1508
  var readAuthJson = async (path) => {
1507
1509
  try {
@@ -1511,6 +1513,22 @@ var readAuthJson = async (path) => {
1511
1513
  return {};
1512
1514
  }
1513
1515
  };
1516
+ var readAllAuthFiles = async (dir) => {
1517
+ const { readdir } = await import('fs/promises');
1518
+ const merged = {};
1519
+ try {
1520
+ const files = await readdir(dir);
1521
+ for (const file of files.filter(isAuthFile)) {
1522
+ const data = await readAuthJson(path.join(dir, file));
1523
+ for (const [provider, entry] of Object.entries(data)) {
1524
+ const key = `${provider}:${file}`;
1525
+ merged[key] = entry;
1526
+ }
1527
+ }
1528
+ } catch {
1529
+ }
1530
+ return merged;
1531
+ };
1514
1532
  var toCredential = (entry) => {
1515
1533
  if (entry.type === "oauth" || entry.refresh !== void 0 && entry.access !== void 0) {
1516
1534
  return {
@@ -1532,22 +1550,23 @@ var entriesEqual = (a, b) => {
1532
1550
  return JSON.stringify(a) === JSON.stringify(b);
1533
1551
  };
1534
1552
  var createAuthWatcher = (options) => {
1535
- const authPath = options?.authJsonPath ?? AUTH_JSON_PATH;
1553
+ const authDir = options?.authJsonPath ? path.dirname(options.authJsonPath) : AUTH_DIR;
1536
1554
  const profPath = options?.profilesPath ?? PROFILES_PATH;
1537
1555
  const log = options?.logger?.child({ component: "profile-watcher" });
1538
1556
  let lastKnownAuth = {};
1539
1557
  let abortController = null;
1540
1558
  let debounceTimer = null;
1541
1559
  const processChange = async () => {
1542
- const newAuth = await readAuthJson(authPath);
1560
+ const newAuth = await readAllAuthFiles(authDir);
1543
1561
  let store = await loadProfiles(profPath);
1544
1562
  let changed = false;
1545
- for (const [provider, entry] of Object.entries(newAuth)) {
1563
+ for (const [compositeKey, entry] of Object.entries(newAuth)) {
1546
1564
  if (!entry) continue;
1547
- const previousEntry = lastKnownAuth[provider];
1565
+ const provider = compositeKey.split(":")[0] ?? compositeKey;
1566
+ const previousEntry = lastKnownAuth[compositeKey];
1548
1567
  if (previousEntry !== void 0 && !entriesEqual(previousEntry, entry)) {
1549
1568
  log?.info(
1550
- { provider, action: "credential_changed" },
1569
+ { provider, source: compositeKey, action: "credential_changed" },
1551
1570
  "Credential change detected \u2014 saving previous credential"
1552
1571
  );
1553
1572
  const prevCredential = toCredential(previousEntry);
@@ -1557,7 +1576,7 @@ var createAuthWatcher = (options) => {
1557
1576
  changed = true;
1558
1577
  }
1559
1578
  } else if (previousEntry === void 0) {
1560
- log?.info({ provider, action: "new_provider" }, "New provider detected");
1579
+ log?.info({ provider, source: compositeKey, action: "new_provider" }, "New auth file detected");
1561
1580
  const credential = toCredential(entry);
1562
1581
  const newStore = addProfile(store, provider, credential);
1563
1582
  if (newStore !== store) {
@@ -1584,32 +1603,32 @@ var createAuthWatcher = (options) => {
1584
1603
  }, DEBOUNCE_MS);
1585
1604
  };
1586
1605
  const start = async () => {
1587
- const currentAuth = await readAuthJson(authPath);
1606
+ const currentAuth = await readAllAuthFiles(authDir);
1588
1607
  const currentStore = await loadProfiles(profPath);
1589
- log?.info({ providers: Object.keys(currentAuth) }, "Auth watcher started");
1608
+ log?.info({ auth_keys: Object.keys(currentAuth).length }, "Auth watcher started");
1590
1609
  if (Object.keys(currentStore).length === 0 && Object.keys(currentAuth).length > 0) {
1591
1610
  let store = {};
1592
- for (const [provider, entry] of Object.entries(currentAuth)) {
1611
+ for (const [compositeKey, entry] of Object.entries(currentAuth)) {
1593
1612
  if (!entry) continue;
1613
+ const provider = compositeKey.split(":")[0] ?? compositeKey;
1594
1614
  const credential = toCredential(entry);
1595
1615
  store = addProfile(store, provider, credential);
1596
1616
  }
1597
1617
  await saveProfiles(store, profPath);
1598
1618
  log?.info(
1599
1619
  { profiles_initialized: Object.keys(store).length },
1600
- "Initialized profiles from auth.json"
1620
+ "Initialized profiles from auth files"
1601
1621
  );
1602
1622
  }
1603
1623
  lastKnownAuth = currentAuth;
1604
1624
  abortController = new AbortController();
1605
- const parentDir = path.dirname(authPath);
1606
1625
  (async () => {
1607
1626
  try {
1608
- const watcher = promises.watch(parentDir, {
1627
+ const watcher = promises.watch(authDir, {
1609
1628
  signal: abortController.signal
1610
1629
  });
1611
1630
  for await (const event of watcher) {
1612
- if (event.filename === "auth.json" || event.filename === null) {
1631
+ if (event.filename !== null && isAuthFile(event.filename)) {
1613
1632
  onFileChange();
1614
1633
  }
1615
1634
  }
@@ -1631,6 +1650,88 @@ var createAuthWatcher = (options) => {
1631
1650
  };
1632
1651
  return { start, stop };
1633
1652
  };
1653
+ var credentialToAuthEntry = (cred) => {
1654
+ if (cred.type === "oauth") {
1655
+ return {
1656
+ type: "oauth",
1657
+ refresh: cred.refresh,
1658
+ access: cred.access,
1659
+ expires: cred.expires,
1660
+ accountId: cred.accountId
1661
+ };
1662
+ }
1663
+ return {
1664
+ type: "api-key",
1665
+ key: cred.key
1666
+ };
1667
+ };
1668
+ var readCurrentAuth = async (authPath) => {
1669
+ try {
1670
+ const content = await promises.readFile(authPath, "utf-8");
1671
+ return JSON.parse(content);
1672
+ } catch {
1673
+ return void 0;
1674
+ }
1675
+ };
1676
+ var switchProfile = async (options) => {
1677
+ const profPath = options.profilesPath ?? PROFILES_PATH;
1678
+ const authPath = options.authJsonPath ?? AUTH_JSON_PATH;
1679
+ const log = options.logger?.child({ component: "profile-switcher" });
1680
+ const store = await loadProfiles(profPath);
1681
+ const target = store[options.targetProfileId];
1682
+ if (target === void 0) {
1683
+ log?.warn({ profileId: options.targetProfileId }, "Profile not found");
1684
+ return { success: false, to: options.targetProfileId, reason: "profile_not_found" };
1685
+ }
1686
+ const currentAuth = await readCurrentAuth(authPath);
1687
+ const currentAccountId = currentAuth?.[target.provider] ? currentAuth[target.provider].accountId : void 0;
1688
+ const newAuth = {};
1689
+ newAuth[target.provider] = credentialToAuthEntry(target.credentials);
1690
+ const dir = path.dirname(authPath);
1691
+ await promises.mkdir(dir, { recursive: true });
1692
+ const tmpPath = `${authPath}.tmp`;
1693
+ await promises.writeFile(tmpPath, JSON.stringify(newAuth, null, 2), "utf-8");
1694
+ await promises.rename(tmpPath, authPath);
1695
+ log?.info(
1696
+ {
1697
+ from: currentAccountId,
1698
+ to: target.credentials.type === "oauth" ? target.credentials.accountId : "(api-key)",
1699
+ profileId: target.id,
1700
+ provider: target.provider
1701
+ },
1702
+ "Switched active profile"
1703
+ );
1704
+ return {
1705
+ success: true,
1706
+ from: currentAccountId ?? void 0,
1707
+ to: target.id
1708
+ };
1709
+ };
1710
+ var switchToNextProfile = async (options) => {
1711
+ const profPath = options.profilesPath ?? PROFILES_PATH;
1712
+ const log = options.logger?.child({ component: "profile-switcher" });
1713
+ const store = await loadProfiles(profPath);
1714
+ const excludeSet = new Set(options.excludeProfileIds ?? []);
1715
+ if (options.currentProfileId) {
1716
+ excludeSet.add(options.currentProfileId);
1717
+ }
1718
+ const candidates = Object.values(store).filter((p) => p.provider === options.provider && !excludeSet.has(p.id)).sort((a, b) => new Date(a.created).getTime() - new Date(b.created).getTime());
1719
+ if (candidates.length === 0) {
1720
+ log?.warn({ provider: options.provider, excluded: [...excludeSet] }, "No available profiles to switch to");
1721
+ return {
1722
+ success: false,
1723
+ to: options.currentProfileId ?? "none",
1724
+ reason: "no_candidates"
1725
+ };
1726
+ }
1727
+ const next = candidates[0];
1728
+ return switchProfile({
1729
+ targetProfileId: next.id,
1730
+ profilesPath: profPath,
1731
+ authJsonPath: options.authJsonPath,
1732
+ logger: options.logger
1733
+ });
1734
+ };
1634
1735
  var { schema: z4 } = tool.tool;
1635
1736
  var createProfileTools = (options) => ({
1636
1737
  profilesList: tool.tool({
@@ -1731,6 +1832,8 @@ exports.removeProfile = removeProfile;
1731
1832
  exports.resumeTarget = resumeTarget;
1732
1833
  exports.saveProfiles = saveProfiles;
1733
1834
  exports.selectTarget = selectTarget;
1835
+ exports.switchProfile = switchProfile;
1836
+ exports.switchToNextProfile = switchToNextProfile;
1734
1837
  exports.updateHealthScore = updateHealthScore;
1735
1838
  exports.updateLatencyEma = updateLatencyEma;
1736
1839
  exports.validateConfig = validateConfig;
@@ -1494,6 +1494,8 @@ var nextProfileId = (store, provider) => {
1494
1494
  return `${provider}-${maxN + 1}`;
1495
1495
  };
1496
1496
  var AUTH_JSON_PATH = join(homedir(), ".local", "share", "opencode", "auth.json");
1497
+ var AUTH_DIR = join(homedir(), ".local", "share", "opencode");
1498
+ var isAuthFile = (filename) => filename === "auth.json" || /^auth-work\d+\.json$/.test(filename);
1497
1499
  var DEBOUNCE_MS = 100;
1498
1500
  var readAuthJson = async (path) => {
1499
1501
  try {
@@ -1503,6 +1505,22 @@ var readAuthJson = async (path) => {
1503
1505
  return {};
1504
1506
  }
1505
1507
  };
1508
+ var readAllAuthFiles = async (dir) => {
1509
+ const { readdir } = await import('fs/promises');
1510
+ const merged = {};
1511
+ try {
1512
+ const files = await readdir(dir);
1513
+ for (const file of files.filter(isAuthFile)) {
1514
+ const data = await readAuthJson(join(dir, file));
1515
+ for (const [provider, entry] of Object.entries(data)) {
1516
+ const key = `${provider}:${file}`;
1517
+ merged[key] = entry;
1518
+ }
1519
+ }
1520
+ } catch {
1521
+ }
1522
+ return merged;
1523
+ };
1506
1524
  var toCredential = (entry) => {
1507
1525
  if (entry.type === "oauth" || entry.refresh !== void 0 && entry.access !== void 0) {
1508
1526
  return {
@@ -1524,22 +1542,23 @@ var entriesEqual = (a, b) => {
1524
1542
  return JSON.stringify(a) === JSON.stringify(b);
1525
1543
  };
1526
1544
  var createAuthWatcher = (options) => {
1527
- const authPath = options?.authJsonPath ?? AUTH_JSON_PATH;
1545
+ const authDir = options?.authJsonPath ? dirname(options.authJsonPath) : AUTH_DIR;
1528
1546
  const profPath = options?.profilesPath ?? PROFILES_PATH;
1529
1547
  const log = options?.logger?.child({ component: "profile-watcher" });
1530
1548
  let lastKnownAuth = {};
1531
1549
  let abortController = null;
1532
1550
  let debounceTimer = null;
1533
1551
  const processChange = async () => {
1534
- const newAuth = await readAuthJson(authPath);
1552
+ const newAuth = await readAllAuthFiles(authDir);
1535
1553
  let store = await loadProfiles(profPath);
1536
1554
  let changed = false;
1537
- for (const [provider, entry] of Object.entries(newAuth)) {
1555
+ for (const [compositeKey, entry] of Object.entries(newAuth)) {
1538
1556
  if (!entry) continue;
1539
- const previousEntry = lastKnownAuth[provider];
1557
+ const provider = compositeKey.split(":")[0] ?? compositeKey;
1558
+ const previousEntry = lastKnownAuth[compositeKey];
1540
1559
  if (previousEntry !== void 0 && !entriesEqual(previousEntry, entry)) {
1541
1560
  log?.info(
1542
- { provider, action: "credential_changed" },
1561
+ { provider, source: compositeKey, action: "credential_changed" },
1543
1562
  "Credential change detected \u2014 saving previous credential"
1544
1563
  );
1545
1564
  const prevCredential = toCredential(previousEntry);
@@ -1549,7 +1568,7 @@ var createAuthWatcher = (options) => {
1549
1568
  changed = true;
1550
1569
  }
1551
1570
  } else if (previousEntry === void 0) {
1552
- log?.info({ provider, action: "new_provider" }, "New provider detected");
1571
+ log?.info({ provider, source: compositeKey, action: "new_provider" }, "New auth file detected");
1553
1572
  const credential = toCredential(entry);
1554
1573
  const newStore = addProfile(store, provider, credential);
1555
1574
  if (newStore !== store) {
@@ -1576,32 +1595,32 @@ var createAuthWatcher = (options) => {
1576
1595
  }, DEBOUNCE_MS);
1577
1596
  };
1578
1597
  const start = async () => {
1579
- const currentAuth = await readAuthJson(authPath);
1598
+ const currentAuth = await readAllAuthFiles(authDir);
1580
1599
  const currentStore = await loadProfiles(profPath);
1581
- log?.info({ providers: Object.keys(currentAuth) }, "Auth watcher started");
1600
+ log?.info({ auth_keys: Object.keys(currentAuth).length }, "Auth watcher started");
1582
1601
  if (Object.keys(currentStore).length === 0 && Object.keys(currentAuth).length > 0) {
1583
1602
  let store = {};
1584
- for (const [provider, entry] of Object.entries(currentAuth)) {
1603
+ for (const [compositeKey, entry] of Object.entries(currentAuth)) {
1585
1604
  if (!entry) continue;
1605
+ const provider = compositeKey.split(":")[0] ?? compositeKey;
1586
1606
  const credential = toCredential(entry);
1587
1607
  store = addProfile(store, provider, credential);
1588
1608
  }
1589
1609
  await saveProfiles(store, profPath);
1590
1610
  log?.info(
1591
1611
  { profiles_initialized: Object.keys(store).length },
1592
- "Initialized profiles from auth.json"
1612
+ "Initialized profiles from auth files"
1593
1613
  );
1594
1614
  }
1595
1615
  lastKnownAuth = currentAuth;
1596
1616
  abortController = new AbortController();
1597
- const parentDir = dirname(authPath);
1598
1617
  (async () => {
1599
1618
  try {
1600
- const watcher = watch(parentDir, {
1619
+ const watcher = watch(authDir, {
1601
1620
  signal: abortController.signal
1602
1621
  });
1603
1622
  for await (const event of watcher) {
1604
- if (event.filename === "auth.json" || event.filename === null) {
1623
+ if (event.filename !== null && isAuthFile(event.filename)) {
1605
1624
  onFileChange();
1606
1625
  }
1607
1626
  }
@@ -1623,6 +1642,88 @@ var createAuthWatcher = (options) => {
1623
1642
  };
1624
1643
  return { start, stop };
1625
1644
  };
1645
+ var credentialToAuthEntry = (cred) => {
1646
+ if (cred.type === "oauth") {
1647
+ return {
1648
+ type: "oauth",
1649
+ refresh: cred.refresh,
1650
+ access: cred.access,
1651
+ expires: cred.expires,
1652
+ accountId: cred.accountId
1653
+ };
1654
+ }
1655
+ return {
1656
+ type: "api-key",
1657
+ key: cred.key
1658
+ };
1659
+ };
1660
+ var readCurrentAuth = async (authPath) => {
1661
+ try {
1662
+ const content = await readFile(authPath, "utf-8");
1663
+ return JSON.parse(content);
1664
+ } catch {
1665
+ return void 0;
1666
+ }
1667
+ };
1668
+ var switchProfile = async (options) => {
1669
+ const profPath = options.profilesPath ?? PROFILES_PATH;
1670
+ const authPath = options.authJsonPath ?? AUTH_JSON_PATH;
1671
+ const log = options.logger?.child({ component: "profile-switcher" });
1672
+ const store = await loadProfiles(profPath);
1673
+ const target = store[options.targetProfileId];
1674
+ if (target === void 0) {
1675
+ log?.warn({ profileId: options.targetProfileId }, "Profile not found");
1676
+ return { success: false, to: options.targetProfileId, reason: "profile_not_found" };
1677
+ }
1678
+ const currentAuth = await readCurrentAuth(authPath);
1679
+ const currentAccountId = currentAuth?.[target.provider] ? currentAuth[target.provider].accountId : void 0;
1680
+ const newAuth = {};
1681
+ newAuth[target.provider] = credentialToAuthEntry(target.credentials);
1682
+ const dir = dirname(authPath);
1683
+ await mkdir(dir, { recursive: true });
1684
+ const tmpPath = `${authPath}.tmp`;
1685
+ await writeFile(tmpPath, JSON.stringify(newAuth, null, 2), "utf-8");
1686
+ await rename(tmpPath, authPath);
1687
+ log?.info(
1688
+ {
1689
+ from: currentAccountId,
1690
+ to: target.credentials.type === "oauth" ? target.credentials.accountId : "(api-key)",
1691
+ profileId: target.id,
1692
+ provider: target.provider
1693
+ },
1694
+ "Switched active profile"
1695
+ );
1696
+ return {
1697
+ success: true,
1698
+ from: currentAccountId ?? void 0,
1699
+ to: target.id
1700
+ };
1701
+ };
1702
+ var switchToNextProfile = async (options) => {
1703
+ const profPath = options.profilesPath ?? PROFILES_PATH;
1704
+ const log = options.logger?.child({ component: "profile-switcher" });
1705
+ const store = await loadProfiles(profPath);
1706
+ const excludeSet = new Set(options.excludeProfileIds ?? []);
1707
+ if (options.currentProfileId) {
1708
+ excludeSet.add(options.currentProfileId);
1709
+ }
1710
+ const candidates = Object.values(store).filter((p) => p.provider === options.provider && !excludeSet.has(p.id)).sort((a, b) => new Date(a.created).getTime() - new Date(b.created).getTime());
1711
+ if (candidates.length === 0) {
1712
+ log?.warn({ provider: options.provider, excluded: [...excludeSet] }, "No available profiles to switch to");
1713
+ return {
1714
+ success: false,
1715
+ to: options.currentProfileId ?? "none",
1716
+ reason: "no_candidates"
1717
+ };
1718
+ }
1719
+ const next = candidates[0];
1720
+ return switchProfile({
1721
+ targetProfileId: next.id,
1722
+ profilesPath: profPath,
1723
+ authJsonPath: options.authJsonPath,
1724
+ logger: options.logger
1725
+ });
1726
+ };
1626
1727
  var { schema: z4 } = tool;
1627
1728
  var createProfileTools = (options) => ({
1628
1729
  profilesList: tool({
@@ -1659,4 +1760,4 @@ var createProfileTools = (options) => ({
1659
1760
  })
1660
1761
  });
1661
1762
 
1662
- export { ADMISSION_RESULTS, BackoffConfigSchema, ConfigValidationError, DEFAULT_ALPHA, DEFAULT_BACKOFF_BASE_MS, DEFAULT_BACKOFF_JITTER, DEFAULT_BACKOFF_MAX_MS, DEFAULT_BACKOFF_MULTIPLIER, DEFAULT_BACKOFF_PARAMS, DEFAULT_FAILOVER_BUDGET, DEFAULT_RETRY, DEFAULT_RETRY_BUDGET, DEFAULT_TIMEOUT_MS, DualBreaker, EXCLUSION_REASONS, ErrorClassSchema, INITIAL_HEALTH_SCORE, REDACT_PATHS, SwitcherConfigSchema, TARGET_STATES, TargetConfigSchema, TargetRegistry, addProfile, applyConfigDiff, checkHardRejects, computeBackoffMs, computeConfigDiff, computeCooldownMs, computeScore, createAdmissionController, createAuditLogger, createAuthWatcher, createCircuitBreaker, createConcurrencyTracker, createCooldownManager, createFailoverOrchestrator, createLogSubscriber, createOperatorTools, createProfileTools, createRegistry, createRequestLogger, createRequestTraceBuffer, createRetryPolicy, createRoutingEventBus, disableTarget, discoverTargets, discoverTargetsFromProfiles, drainTarget, generateCorrelationId, getExclusionReason, getTargetStateTransition, inspectRequest, isRetryable, listProfiles, listTargets, loadProfiles, nextProfileId, normalizeLatency, pauseTarget, reloadConfig, removeProfile, resumeTarget, saveProfiles, selectTarget, updateHealthScore, updateLatencyEma, validateConfig };
1763
+ export { ADMISSION_RESULTS, BackoffConfigSchema, ConfigValidationError, DEFAULT_ALPHA, DEFAULT_BACKOFF_BASE_MS, DEFAULT_BACKOFF_JITTER, DEFAULT_BACKOFF_MAX_MS, DEFAULT_BACKOFF_MULTIPLIER, DEFAULT_BACKOFF_PARAMS, DEFAULT_FAILOVER_BUDGET, DEFAULT_RETRY, DEFAULT_RETRY_BUDGET, DEFAULT_TIMEOUT_MS, DualBreaker, EXCLUSION_REASONS, ErrorClassSchema, INITIAL_HEALTH_SCORE, REDACT_PATHS, SwitcherConfigSchema, TARGET_STATES, TargetConfigSchema, TargetRegistry, addProfile, applyConfigDiff, checkHardRejects, computeBackoffMs, computeConfigDiff, computeCooldownMs, computeScore, createAdmissionController, createAuditLogger, createAuthWatcher, createCircuitBreaker, createConcurrencyTracker, createCooldownManager, createFailoverOrchestrator, createLogSubscriber, createOperatorTools, createProfileTools, createRegistry, createRequestLogger, createRequestTraceBuffer, createRetryPolicy, createRoutingEventBus, disableTarget, discoverTargets, discoverTargetsFromProfiles, drainTarget, generateCorrelationId, getExclusionReason, getTargetStateTransition, inspectRequest, isRetryable, listProfiles, listTargets, loadProfiles, nextProfileId, normalizeLatency, pauseTarget, reloadConfig, removeProfile, resumeTarget, saveProfiles, selectTarget, switchProfile, switchToNextProfile, updateHealthScore, updateLatencyEma, validateConfig };
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkTJ7ZGZHD_cjs = require('./chunk-TJ7ZGZHD.cjs');
3
+ var chunkH72U2MNG_cjs = require('./chunk-H72U2MNG.cjs');
4
4
  var crypto = require('crypto');
5
5
 
6
6
  // src/mode/detection.ts
@@ -631,7 +631,7 @@ var createStreamStitcher = (_requestId) => {
631
631
 
632
632
  // src/execution/audit-collector.ts
633
633
  var createAuditCollector = (logger, requestId) => {
634
- const requestLogger = chunkTJ7ZGZHD_cjs.createRequestLogger(logger, requestId);
634
+ const requestLogger = chunkH72U2MNG_cjs.createRequestLogger(logger, requestId);
635
635
  const attempts = [];
636
636
  const segments = [];
637
637
  return {
@@ -661,7 +661,7 @@ var createAuditCollector = (logger, requestId) => {
661
661
  // src/execution/orchestrator.ts
662
662
  var createExecutionOrchestrator = (deps) => ({
663
663
  async execute(request) {
664
- const requestId = request.request_id || chunkTJ7ZGZHD_cjs.generateCorrelationId();
664
+ const requestId = request.request_id || chunkH72U2MNG_cjs.generateCorrelationId();
665
665
  const auditCollector = createAuditCollector(deps.logger, requestId);
666
666
  const stitcher = createStreamStitcher();
667
667
  const startMs = Date.now();
@@ -760,7 +760,7 @@ var createExecutionOrchestrator = (deps) => ({
760
760
  };
761
761
  } finally {
762
762
  auditCollector.flush(outcome, finalTarget);
763
- const requestLogger = chunkTJ7ZGZHD_cjs.createRequestLogger(deps.logger, requestId);
763
+ const requestLogger = chunkH72U2MNG_cjs.createRequestLogger(deps.logger, requestId);
764
764
  const endMs = Date.now();
765
765
  requestLogger.info(
766
766
  {
@@ -891,271 +891,279 @@ var validateBearerToken = (authHeader, expectedToken) => {
891
891
 
892
892
  Object.defineProperty(exports, "ADMISSION_RESULTS", {
893
893
  enumerable: true,
894
- get: function () { return chunkTJ7ZGZHD_cjs.ADMISSION_RESULTS; }
894
+ get: function () { return chunkH72U2MNG_cjs.ADMISSION_RESULTS; }
895
895
  });
896
896
  Object.defineProperty(exports, "BackoffConfigSchema", {
897
897
  enumerable: true,
898
- get: function () { return chunkTJ7ZGZHD_cjs.BackoffConfigSchema; }
898
+ get: function () { return chunkH72U2MNG_cjs.BackoffConfigSchema; }
899
899
  });
900
900
  Object.defineProperty(exports, "ConfigValidationError", {
901
901
  enumerable: true,
902
- get: function () { return chunkTJ7ZGZHD_cjs.ConfigValidationError; }
902
+ get: function () { return chunkH72U2MNG_cjs.ConfigValidationError; }
903
903
  });
904
904
  Object.defineProperty(exports, "DEFAULT_ALPHA", {
905
905
  enumerable: true,
906
- get: function () { return chunkTJ7ZGZHD_cjs.DEFAULT_ALPHA; }
906
+ get: function () { return chunkH72U2MNG_cjs.DEFAULT_ALPHA; }
907
907
  });
908
908
  Object.defineProperty(exports, "DEFAULT_BACKOFF_BASE_MS", {
909
909
  enumerable: true,
910
- get: function () { return chunkTJ7ZGZHD_cjs.DEFAULT_BACKOFF_BASE_MS; }
910
+ get: function () { return chunkH72U2MNG_cjs.DEFAULT_BACKOFF_BASE_MS; }
911
911
  });
912
912
  Object.defineProperty(exports, "DEFAULT_BACKOFF_JITTER", {
913
913
  enumerable: true,
914
- get: function () { return chunkTJ7ZGZHD_cjs.DEFAULT_BACKOFF_JITTER; }
914
+ get: function () { return chunkH72U2MNG_cjs.DEFAULT_BACKOFF_JITTER; }
915
915
  });
916
916
  Object.defineProperty(exports, "DEFAULT_BACKOFF_MAX_MS", {
917
917
  enumerable: true,
918
- get: function () { return chunkTJ7ZGZHD_cjs.DEFAULT_BACKOFF_MAX_MS; }
918
+ get: function () { return chunkH72U2MNG_cjs.DEFAULT_BACKOFF_MAX_MS; }
919
919
  });
920
920
  Object.defineProperty(exports, "DEFAULT_BACKOFF_MULTIPLIER", {
921
921
  enumerable: true,
922
- get: function () { return chunkTJ7ZGZHD_cjs.DEFAULT_BACKOFF_MULTIPLIER; }
922
+ get: function () { return chunkH72U2MNG_cjs.DEFAULT_BACKOFF_MULTIPLIER; }
923
923
  });
924
924
  Object.defineProperty(exports, "DEFAULT_BACKOFF_PARAMS", {
925
925
  enumerable: true,
926
- get: function () { return chunkTJ7ZGZHD_cjs.DEFAULT_BACKOFF_PARAMS; }
926
+ get: function () { return chunkH72U2MNG_cjs.DEFAULT_BACKOFF_PARAMS; }
927
927
  });
928
928
  Object.defineProperty(exports, "DEFAULT_FAILOVER_BUDGET", {
929
929
  enumerable: true,
930
- get: function () { return chunkTJ7ZGZHD_cjs.DEFAULT_FAILOVER_BUDGET; }
930
+ get: function () { return chunkH72U2MNG_cjs.DEFAULT_FAILOVER_BUDGET; }
931
931
  });
932
932
  Object.defineProperty(exports, "DEFAULT_RETRY", {
933
933
  enumerable: true,
934
- get: function () { return chunkTJ7ZGZHD_cjs.DEFAULT_RETRY; }
934
+ get: function () { return chunkH72U2MNG_cjs.DEFAULT_RETRY; }
935
935
  });
936
936
  Object.defineProperty(exports, "DEFAULT_RETRY_BUDGET", {
937
937
  enumerable: true,
938
- get: function () { return chunkTJ7ZGZHD_cjs.DEFAULT_RETRY_BUDGET; }
938
+ get: function () { return chunkH72U2MNG_cjs.DEFAULT_RETRY_BUDGET; }
939
939
  });
940
940
  Object.defineProperty(exports, "DEFAULT_TIMEOUT_MS", {
941
941
  enumerable: true,
942
- get: function () { return chunkTJ7ZGZHD_cjs.DEFAULT_TIMEOUT_MS; }
942
+ get: function () { return chunkH72U2MNG_cjs.DEFAULT_TIMEOUT_MS; }
943
943
  });
944
944
  Object.defineProperty(exports, "DualBreaker", {
945
945
  enumerable: true,
946
- get: function () { return chunkTJ7ZGZHD_cjs.DualBreaker; }
946
+ get: function () { return chunkH72U2MNG_cjs.DualBreaker; }
947
947
  });
948
948
  Object.defineProperty(exports, "EXCLUSION_REASONS", {
949
949
  enumerable: true,
950
- get: function () { return chunkTJ7ZGZHD_cjs.EXCLUSION_REASONS; }
950
+ get: function () { return chunkH72U2MNG_cjs.EXCLUSION_REASONS; }
951
951
  });
952
952
  Object.defineProperty(exports, "ErrorClassSchema", {
953
953
  enumerable: true,
954
- get: function () { return chunkTJ7ZGZHD_cjs.ErrorClassSchema; }
954
+ get: function () { return chunkH72U2MNG_cjs.ErrorClassSchema; }
955
955
  });
956
956
  Object.defineProperty(exports, "INITIAL_HEALTH_SCORE", {
957
957
  enumerable: true,
958
- get: function () { return chunkTJ7ZGZHD_cjs.INITIAL_HEALTH_SCORE; }
958
+ get: function () { return chunkH72U2MNG_cjs.INITIAL_HEALTH_SCORE; }
959
959
  });
960
960
  Object.defineProperty(exports, "REDACT_PATHS", {
961
961
  enumerable: true,
962
- get: function () { return chunkTJ7ZGZHD_cjs.REDACT_PATHS; }
962
+ get: function () { return chunkH72U2MNG_cjs.REDACT_PATHS; }
963
963
  });
964
964
  Object.defineProperty(exports, "SwitcherConfigSchema", {
965
965
  enumerable: true,
966
- get: function () { return chunkTJ7ZGZHD_cjs.SwitcherConfigSchema; }
966
+ get: function () { return chunkH72U2MNG_cjs.SwitcherConfigSchema; }
967
967
  });
968
968
  Object.defineProperty(exports, "TARGET_STATES", {
969
969
  enumerable: true,
970
- get: function () { return chunkTJ7ZGZHD_cjs.TARGET_STATES; }
970
+ get: function () { return chunkH72U2MNG_cjs.TARGET_STATES; }
971
971
  });
972
972
  Object.defineProperty(exports, "TargetConfigSchema", {
973
973
  enumerable: true,
974
- get: function () { return chunkTJ7ZGZHD_cjs.TargetConfigSchema; }
974
+ get: function () { return chunkH72U2MNG_cjs.TargetConfigSchema; }
975
975
  });
976
976
  Object.defineProperty(exports, "TargetRegistry", {
977
977
  enumerable: true,
978
- get: function () { return chunkTJ7ZGZHD_cjs.TargetRegistry; }
978
+ get: function () { return chunkH72U2MNG_cjs.TargetRegistry; }
979
979
  });
980
980
  Object.defineProperty(exports, "addProfile", {
981
981
  enumerable: true,
982
- get: function () { return chunkTJ7ZGZHD_cjs.addProfile; }
982
+ get: function () { return chunkH72U2MNG_cjs.addProfile; }
983
983
  });
984
984
  Object.defineProperty(exports, "applyConfigDiff", {
985
985
  enumerable: true,
986
- get: function () { return chunkTJ7ZGZHD_cjs.applyConfigDiff; }
986
+ get: function () { return chunkH72U2MNG_cjs.applyConfigDiff; }
987
987
  });
988
988
  Object.defineProperty(exports, "checkHardRejects", {
989
989
  enumerable: true,
990
- get: function () { return chunkTJ7ZGZHD_cjs.checkHardRejects; }
990
+ get: function () { return chunkH72U2MNG_cjs.checkHardRejects; }
991
991
  });
992
992
  Object.defineProperty(exports, "computeBackoffMs", {
993
993
  enumerable: true,
994
- get: function () { return chunkTJ7ZGZHD_cjs.computeBackoffMs; }
994
+ get: function () { return chunkH72U2MNG_cjs.computeBackoffMs; }
995
995
  });
996
996
  Object.defineProperty(exports, "computeConfigDiff", {
997
997
  enumerable: true,
998
- get: function () { return chunkTJ7ZGZHD_cjs.computeConfigDiff; }
998
+ get: function () { return chunkH72U2MNG_cjs.computeConfigDiff; }
999
999
  });
1000
1000
  Object.defineProperty(exports, "computeCooldownMs", {
1001
1001
  enumerable: true,
1002
- get: function () { return chunkTJ7ZGZHD_cjs.computeCooldownMs; }
1002
+ get: function () { return chunkH72U2MNG_cjs.computeCooldownMs; }
1003
1003
  });
1004
1004
  Object.defineProperty(exports, "computeScore", {
1005
1005
  enumerable: true,
1006
- get: function () { return chunkTJ7ZGZHD_cjs.computeScore; }
1006
+ get: function () { return chunkH72U2MNG_cjs.computeScore; }
1007
1007
  });
1008
1008
  Object.defineProperty(exports, "createAdmissionController", {
1009
1009
  enumerable: true,
1010
- get: function () { return chunkTJ7ZGZHD_cjs.createAdmissionController; }
1010
+ get: function () { return chunkH72U2MNG_cjs.createAdmissionController; }
1011
1011
  });
1012
1012
  Object.defineProperty(exports, "createAuditLogger", {
1013
1013
  enumerable: true,
1014
- get: function () { return chunkTJ7ZGZHD_cjs.createAuditLogger; }
1014
+ get: function () { return chunkH72U2MNG_cjs.createAuditLogger; }
1015
1015
  });
1016
1016
  Object.defineProperty(exports, "createAuthWatcher", {
1017
1017
  enumerable: true,
1018
- get: function () { return chunkTJ7ZGZHD_cjs.createAuthWatcher; }
1018
+ get: function () { return chunkH72U2MNG_cjs.createAuthWatcher; }
1019
1019
  });
1020
1020
  Object.defineProperty(exports, "createCircuitBreaker", {
1021
1021
  enumerable: true,
1022
- get: function () { return chunkTJ7ZGZHD_cjs.createCircuitBreaker; }
1022
+ get: function () { return chunkH72U2MNG_cjs.createCircuitBreaker; }
1023
1023
  });
1024
1024
  Object.defineProperty(exports, "createConcurrencyTracker", {
1025
1025
  enumerable: true,
1026
- get: function () { return chunkTJ7ZGZHD_cjs.createConcurrencyTracker; }
1026
+ get: function () { return chunkH72U2MNG_cjs.createConcurrencyTracker; }
1027
1027
  });
1028
1028
  Object.defineProperty(exports, "createCooldownManager", {
1029
1029
  enumerable: true,
1030
- get: function () { return chunkTJ7ZGZHD_cjs.createCooldownManager; }
1030
+ get: function () { return chunkH72U2MNG_cjs.createCooldownManager; }
1031
1031
  });
1032
1032
  Object.defineProperty(exports, "createFailoverOrchestrator", {
1033
1033
  enumerable: true,
1034
- get: function () { return chunkTJ7ZGZHD_cjs.createFailoverOrchestrator; }
1034
+ get: function () { return chunkH72U2MNG_cjs.createFailoverOrchestrator; }
1035
1035
  });
1036
1036
  Object.defineProperty(exports, "createLogSubscriber", {
1037
1037
  enumerable: true,
1038
- get: function () { return chunkTJ7ZGZHD_cjs.createLogSubscriber; }
1038
+ get: function () { return chunkH72U2MNG_cjs.createLogSubscriber; }
1039
1039
  });
1040
1040
  Object.defineProperty(exports, "createOperatorTools", {
1041
1041
  enumerable: true,
1042
- get: function () { return chunkTJ7ZGZHD_cjs.createOperatorTools; }
1042
+ get: function () { return chunkH72U2MNG_cjs.createOperatorTools; }
1043
1043
  });
1044
1044
  Object.defineProperty(exports, "createProfileTools", {
1045
1045
  enumerable: true,
1046
- get: function () { return chunkTJ7ZGZHD_cjs.createProfileTools; }
1046
+ get: function () { return chunkH72U2MNG_cjs.createProfileTools; }
1047
1047
  });
1048
1048
  Object.defineProperty(exports, "createRegistry", {
1049
1049
  enumerable: true,
1050
- get: function () { return chunkTJ7ZGZHD_cjs.createRegistry; }
1050
+ get: function () { return chunkH72U2MNG_cjs.createRegistry; }
1051
1051
  });
1052
1052
  Object.defineProperty(exports, "createRequestLogger", {
1053
1053
  enumerable: true,
1054
- get: function () { return chunkTJ7ZGZHD_cjs.createRequestLogger; }
1054
+ get: function () { return chunkH72U2MNG_cjs.createRequestLogger; }
1055
1055
  });
1056
1056
  Object.defineProperty(exports, "createRequestTraceBuffer", {
1057
1057
  enumerable: true,
1058
- get: function () { return chunkTJ7ZGZHD_cjs.createRequestTraceBuffer; }
1058
+ get: function () { return chunkH72U2MNG_cjs.createRequestTraceBuffer; }
1059
1059
  });
1060
1060
  Object.defineProperty(exports, "createRetryPolicy", {
1061
1061
  enumerable: true,
1062
- get: function () { return chunkTJ7ZGZHD_cjs.createRetryPolicy; }
1062
+ get: function () { return chunkH72U2MNG_cjs.createRetryPolicy; }
1063
1063
  });
1064
1064
  Object.defineProperty(exports, "createRoutingEventBus", {
1065
1065
  enumerable: true,
1066
- get: function () { return chunkTJ7ZGZHD_cjs.createRoutingEventBus; }
1066
+ get: function () { return chunkH72U2MNG_cjs.createRoutingEventBus; }
1067
1067
  });
1068
1068
  Object.defineProperty(exports, "disableTarget", {
1069
1069
  enumerable: true,
1070
- get: function () { return chunkTJ7ZGZHD_cjs.disableTarget; }
1070
+ get: function () { return chunkH72U2MNG_cjs.disableTarget; }
1071
1071
  });
1072
1072
  Object.defineProperty(exports, "discoverTargets", {
1073
1073
  enumerable: true,
1074
- get: function () { return chunkTJ7ZGZHD_cjs.discoverTargets; }
1074
+ get: function () { return chunkH72U2MNG_cjs.discoverTargets; }
1075
1075
  });
1076
1076
  Object.defineProperty(exports, "discoverTargetsFromProfiles", {
1077
1077
  enumerable: true,
1078
- get: function () { return chunkTJ7ZGZHD_cjs.discoverTargetsFromProfiles; }
1078
+ get: function () { return chunkH72U2MNG_cjs.discoverTargetsFromProfiles; }
1079
1079
  });
1080
1080
  Object.defineProperty(exports, "drainTarget", {
1081
1081
  enumerable: true,
1082
- get: function () { return chunkTJ7ZGZHD_cjs.drainTarget; }
1082
+ get: function () { return chunkH72U2MNG_cjs.drainTarget; }
1083
1083
  });
1084
1084
  Object.defineProperty(exports, "generateCorrelationId", {
1085
1085
  enumerable: true,
1086
- get: function () { return chunkTJ7ZGZHD_cjs.generateCorrelationId; }
1086
+ get: function () { return chunkH72U2MNG_cjs.generateCorrelationId; }
1087
1087
  });
1088
1088
  Object.defineProperty(exports, "getExclusionReason", {
1089
1089
  enumerable: true,
1090
- get: function () { return chunkTJ7ZGZHD_cjs.getExclusionReason; }
1090
+ get: function () { return chunkH72U2MNG_cjs.getExclusionReason; }
1091
1091
  });
1092
1092
  Object.defineProperty(exports, "getTargetStateTransition", {
1093
1093
  enumerable: true,
1094
- get: function () { return chunkTJ7ZGZHD_cjs.getTargetStateTransition; }
1094
+ get: function () { return chunkH72U2MNG_cjs.getTargetStateTransition; }
1095
1095
  });
1096
1096
  Object.defineProperty(exports, "inspectRequest", {
1097
1097
  enumerable: true,
1098
- get: function () { return chunkTJ7ZGZHD_cjs.inspectRequest; }
1098
+ get: function () { return chunkH72U2MNG_cjs.inspectRequest; }
1099
1099
  });
1100
1100
  Object.defineProperty(exports, "isRetryable", {
1101
1101
  enumerable: true,
1102
- get: function () { return chunkTJ7ZGZHD_cjs.isRetryable; }
1102
+ get: function () { return chunkH72U2MNG_cjs.isRetryable; }
1103
1103
  });
1104
1104
  Object.defineProperty(exports, "listProfiles", {
1105
1105
  enumerable: true,
1106
- get: function () { return chunkTJ7ZGZHD_cjs.listProfiles; }
1106
+ get: function () { return chunkH72U2MNG_cjs.listProfiles; }
1107
1107
  });
1108
1108
  Object.defineProperty(exports, "listTargets", {
1109
1109
  enumerable: true,
1110
- get: function () { return chunkTJ7ZGZHD_cjs.listTargets; }
1110
+ get: function () { return chunkH72U2MNG_cjs.listTargets; }
1111
1111
  });
1112
1112
  Object.defineProperty(exports, "loadProfiles", {
1113
1113
  enumerable: true,
1114
- get: function () { return chunkTJ7ZGZHD_cjs.loadProfiles; }
1114
+ get: function () { return chunkH72U2MNG_cjs.loadProfiles; }
1115
1115
  });
1116
1116
  Object.defineProperty(exports, "nextProfileId", {
1117
1117
  enumerable: true,
1118
- get: function () { return chunkTJ7ZGZHD_cjs.nextProfileId; }
1118
+ get: function () { return chunkH72U2MNG_cjs.nextProfileId; }
1119
1119
  });
1120
1120
  Object.defineProperty(exports, "normalizeLatency", {
1121
1121
  enumerable: true,
1122
- get: function () { return chunkTJ7ZGZHD_cjs.normalizeLatency; }
1122
+ get: function () { return chunkH72U2MNG_cjs.normalizeLatency; }
1123
1123
  });
1124
1124
  Object.defineProperty(exports, "pauseTarget", {
1125
1125
  enumerable: true,
1126
- get: function () { return chunkTJ7ZGZHD_cjs.pauseTarget; }
1126
+ get: function () { return chunkH72U2MNG_cjs.pauseTarget; }
1127
1127
  });
1128
1128
  Object.defineProperty(exports, "reloadConfig", {
1129
1129
  enumerable: true,
1130
- get: function () { return chunkTJ7ZGZHD_cjs.reloadConfig; }
1130
+ get: function () { return chunkH72U2MNG_cjs.reloadConfig; }
1131
1131
  });
1132
1132
  Object.defineProperty(exports, "removeProfile", {
1133
1133
  enumerable: true,
1134
- get: function () { return chunkTJ7ZGZHD_cjs.removeProfile; }
1134
+ get: function () { return chunkH72U2MNG_cjs.removeProfile; }
1135
1135
  });
1136
1136
  Object.defineProperty(exports, "resumeTarget", {
1137
1137
  enumerable: true,
1138
- get: function () { return chunkTJ7ZGZHD_cjs.resumeTarget; }
1138
+ get: function () { return chunkH72U2MNG_cjs.resumeTarget; }
1139
1139
  });
1140
1140
  Object.defineProperty(exports, "saveProfiles", {
1141
1141
  enumerable: true,
1142
- get: function () { return chunkTJ7ZGZHD_cjs.saveProfiles; }
1142
+ get: function () { return chunkH72U2MNG_cjs.saveProfiles; }
1143
1143
  });
1144
1144
  Object.defineProperty(exports, "selectTarget", {
1145
1145
  enumerable: true,
1146
- get: function () { return chunkTJ7ZGZHD_cjs.selectTarget; }
1146
+ get: function () { return chunkH72U2MNG_cjs.selectTarget; }
1147
+ });
1148
+ Object.defineProperty(exports, "switchProfile", {
1149
+ enumerable: true,
1150
+ get: function () { return chunkH72U2MNG_cjs.switchProfile; }
1151
+ });
1152
+ Object.defineProperty(exports, "switchToNextProfile", {
1153
+ enumerable: true,
1154
+ get: function () { return chunkH72U2MNG_cjs.switchToNextProfile; }
1147
1155
  });
1148
1156
  Object.defineProperty(exports, "updateHealthScore", {
1149
1157
  enumerable: true,
1150
- get: function () { return chunkTJ7ZGZHD_cjs.updateHealthScore; }
1158
+ get: function () { return chunkH72U2MNG_cjs.updateHealthScore; }
1151
1159
  });
1152
1160
  Object.defineProperty(exports, "updateLatencyEma", {
1153
1161
  enumerable: true,
1154
- get: function () { return chunkTJ7ZGZHD_cjs.updateLatencyEma; }
1162
+ get: function () { return chunkH72U2MNG_cjs.updateLatencyEma; }
1155
1163
  });
1156
1164
  Object.defineProperty(exports, "validateConfig", {
1157
1165
  enumerable: true,
1158
- get: function () { return chunkTJ7ZGZHD_cjs.validateConfig; }
1166
+ get: function () { return chunkH72U2MNG_cjs.validateConfig; }
1159
1167
  });
1160
1168
  exports.HEURISTIC_PATTERNS = HEURISTIC_PATTERNS;
1161
1169
  exports.PROVIDER_PATTERNS = PROVIDER_PATTERNS;
package/dist/index.d.cts CHANGED
@@ -1989,6 +1989,48 @@ interface AuthWatcherOptions {
1989
1989
  */
1990
1990
  declare const createAuthWatcher: (options?: AuthWatcherOptions) => AuthWatcher;
1991
1991
 
1992
+ /**
1993
+ * Profile switcher — writes the selected profile's credentials into
1994
+ * OpenCode's auth.json so the next request (or new window) picks them up.
1995
+ *
1996
+ * Also updates .active-openai-auth when switching between auth-work* files.
1997
+ */
1998
+
1999
+ /** Result of a profile switch attempt. */
2000
+ interface SwitchResult {
2001
+ readonly success: boolean;
2002
+ readonly from?: string;
2003
+ readonly to: string;
2004
+ readonly reason?: string;
2005
+ }
2006
+ /**
2007
+ * Switches the active profile by writing its credentials to auth.json.
2008
+ *
2009
+ * 1. Reads the target profile from profiles.json
2010
+ * 2. Writes its credentials to auth.json (atomic: tmp + rename)
2011
+ * 3. Updates .active-openai-auth if applicable
2012
+ */
2013
+ declare const switchProfile: (options: {
2014
+ readonly targetProfileId: string;
2015
+ readonly profilesPath?: string;
2016
+ readonly authJsonPath?: string;
2017
+ readonly logger?: pino.Logger;
2018
+ }) => Promise<SwitchResult>;
2019
+ /**
2020
+ * Picks the next healthy profile for a provider and switches to it.
2021
+ *
2022
+ * Selection strategy: round-robin among profiles that are NOT the current one.
2023
+ * The caller (plugin.ts) provides health data to skip unhealthy profiles.
2024
+ */
2025
+ declare const switchToNextProfile: (options: {
2026
+ readonly provider: string;
2027
+ readonly currentProfileId?: string;
2028
+ readonly excludeProfileIds?: ReadonlyArray<string>;
2029
+ readonly profilesPath?: string;
2030
+ readonly authJsonPath?: string;
2031
+ readonly logger?: pino.Logger;
2032
+ }) => Promise<SwitchResult>;
2033
+
1992
2034
  /**
1993
2035
  * OpenCode plugin tool definitions for profile management.
1994
2036
  *
@@ -2018,4 +2060,4 @@ interface ProfileToolsOptions {
2018
2060
  */
2019
2061
  declare const createProfileTools: (options?: ProfileToolsOptions) => ProfileTools;
2020
2062
 
2021
- export { ADMISSION_RESULTS, type AdapterRequest, type AdapterResult, type AdmissionConfig, type AdmissionContext, type AdmissionController, type AdmissionDecision, type AdmissionResult, type ApiKeyCredential, type AttemptFn, type AuditCollector, type AuditEvent, type AuditLoggerOptions, type AuthCredential, type AuthResult, type AuthWatcher, type AuthWatcherOptions, type BackoffConfig, BackoffConfigSchema, type BackoffParams, type CircuitBreaker, type CircuitBreakerConfig, type ClassificationResult, type ConcurrencyTrackerApi, type ConfigDiagnostic, type ConfigDiff, type ConfigRef, ConfigValidationError, type ContinuationMode, type CooldownManager, type CorrelationId, DEFAULT_ALPHA, DEFAULT_BACKOFF_BASE_MS, DEFAULT_BACKOFF_JITTER, DEFAULT_BACKOFF_MAX_MS, DEFAULT_BACKOFF_MULTIPLIER, DEFAULT_BACKOFF_PARAMS, DEFAULT_FAILOVER_BUDGET, DEFAULT_RETRY, DEFAULT_RETRY_BUDGET, DEFAULT_TIMEOUT_MS, type DeploymentMode, DualBreaker, EXCLUSION_REASONS, type ErrorClass, ErrorClassSchema, type ExclusionReason, type ExecutionDeps, type ExecutionOrchestrator, type ExecutionProvenance, type ExecutionResult, type FailoverAttempt, type FailoverDeps, type FailoverOrchestrator, type FailoverRegistry, type FailoverResult, HEURISTIC_PATTERNS, INITIAL_HEALTH_SCORE, type InspectResult, type ListTargetsResult, type ModeAdapter, type ModeCapabilities, type NormalizedSignal, type OAuthCredential, type OperatorDeps, type OperatorTools, PROVIDER_PATTERNS, type ProfileEntry, type ProfileStore, type ProfileTools, type ProfileToolsOptions, type ProviderConfigLike, REDACT_PATHS, type RegistrySnapshot, type ReloadResult, type RequestTraceBuffer, type RequestTraceEntry, type RetryDecision, type RetryPolicy, type RoutingEventMap, type RoutingWeights, type SegmentProvenance, type SelectionRecord, type SignalFidelity, type StitchedOutput, type StreamBuffer, type StreamChunk, type StreamStitcher, type SwitcherConfig, SwitcherConfigSchema, TARGET_STATES, TEMPORAL_QUOTA_PATTERN, type TargetActionResult, type TargetConfig, TargetConfigSchema, type TargetEntry, TargetRegistry, type TargetState, type TargetView, addProfile, applyConfigDiff, checkHardRejects, classify, computeBackoffMs, computeConfigDiff, computeCooldownMs, computeScore, createAdmissionController, createAuditCollector, createAuditLogger, createAuthWatcher, createCircuitBreaker, createConcurrencyTracker, createCooldownManager, createExecutionOrchestrator, createFailoverOrchestrator, createLogSubscriber, createModeAdapter, createOperatorTools, createProfileTools, createRegistry, createRequestLogger, createRequestTraceBuffer, createRetryPolicy, createRoutingEventBus, createStreamBuffer, createStreamStitcher, detectDeploymentMode, determineContinuationMode, directSignalFromResponse, disableTarget, discoverTargets, discoverTargetsFromProfiles, drainTarget, extractRetryAfterMs, generateCorrelationId, getExclusionReason, getModeCapabilities, getSignalFidelity, getTargetStateTransition, heuristicSignalFromEvent, inspectRequest, isRetryable, listProfiles, listTargets, loadProfiles, nextProfileId, normalizeLatency, pauseTarget, reloadConfig, removeProfile, resumeTarget, saveProfiles, selectTarget, updateHealthScore, updateLatencyEma, validateBearerToken, validateConfig };
2063
+ export { ADMISSION_RESULTS, type AdapterRequest, type AdapterResult, type AdmissionConfig, type AdmissionContext, type AdmissionController, type AdmissionDecision, type AdmissionResult, type ApiKeyCredential, type AttemptFn, type AuditCollector, type AuditEvent, type AuditLoggerOptions, type AuthCredential, type AuthResult, type AuthWatcher, type AuthWatcherOptions, type BackoffConfig, BackoffConfigSchema, type BackoffParams, type CircuitBreaker, type CircuitBreakerConfig, type ClassificationResult, type ConcurrencyTrackerApi, type ConfigDiagnostic, type ConfigDiff, type ConfigRef, ConfigValidationError, type ContinuationMode, type CooldownManager, type CorrelationId, DEFAULT_ALPHA, DEFAULT_BACKOFF_BASE_MS, DEFAULT_BACKOFF_JITTER, DEFAULT_BACKOFF_MAX_MS, DEFAULT_BACKOFF_MULTIPLIER, DEFAULT_BACKOFF_PARAMS, DEFAULT_FAILOVER_BUDGET, DEFAULT_RETRY, DEFAULT_RETRY_BUDGET, DEFAULT_TIMEOUT_MS, type DeploymentMode, DualBreaker, EXCLUSION_REASONS, type ErrorClass, ErrorClassSchema, type ExclusionReason, type ExecutionDeps, type ExecutionOrchestrator, type ExecutionProvenance, type ExecutionResult, type FailoverAttempt, type FailoverDeps, type FailoverOrchestrator, type FailoverRegistry, type FailoverResult, HEURISTIC_PATTERNS, INITIAL_HEALTH_SCORE, type InspectResult, type ListTargetsResult, type ModeAdapter, type ModeCapabilities, type NormalizedSignal, type OAuthCredential, type OperatorDeps, type OperatorTools, PROVIDER_PATTERNS, type ProfileEntry, type ProfileStore, type ProfileTools, type ProfileToolsOptions, type ProviderConfigLike, REDACT_PATHS, type RegistrySnapshot, type ReloadResult, type RequestTraceBuffer, type RequestTraceEntry, type RetryDecision, type RetryPolicy, type RoutingEventMap, type RoutingWeights, type SegmentProvenance, type SelectionRecord, type SignalFidelity, type StitchedOutput, type StreamBuffer, type StreamChunk, type StreamStitcher, type SwitchResult, type SwitcherConfig, SwitcherConfigSchema, TARGET_STATES, TEMPORAL_QUOTA_PATTERN, type TargetActionResult, type TargetConfig, TargetConfigSchema, type TargetEntry, TargetRegistry, type TargetState, type TargetView, addProfile, applyConfigDiff, checkHardRejects, classify, computeBackoffMs, computeConfigDiff, computeCooldownMs, computeScore, createAdmissionController, createAuditCollector, createAuditLogger, createAuthWatcher, createCircuitBreaker, createConcurrencyTracker, createCooldownManager, createExecutionOrchestrator, createFailoverOrchestrator, createLogSubscriber, createModeAdapter, createOperatorTools, createProfileTools, createRegistry, createRequestLogger, createRequestTraceBuffer, createRetryPolicy, createRoutingEventBus, createStreamBuffer, createStreamStitcher, detectDeploymentMode, determineContinuationMode, directSignalFromResponse, disableTarget, discoverTargets, discoverTargetsFromProfiles, drainTarget, extractRetryAfterMs, generateCorrelationId, getExclusionReason, getModeCapabilities, getSignalFidelity, getTargetStateTransition, heuristicSignalFromEvent, inspectRequest, isRetryable, listProfiles, listTargets, loadProfiles, nextProfileId, normalizeLatency, pauseTarget, reloadConfig, removeProfile, resumeTarget, saveProfiles, selectTarget, switchProfile, switchToNextProfile, updateHealthScore, updateLatencyEma, validateBearerToken, validateConfig };
package/dist/index.d.ts CHANGED
@@ -1989,6 +1989,48 @@ interface AuthWatcherOptions {
1989
1989
  */
1990
1990
  declare const createAuthWatcher: (options?: AuthWatcherOptions) => AuthWatcher;
1991
1991
 
1992
+ /**
1993
+ * Profile switcher — writes the selected profile's credentials into
1994
+ * OpenCode's auth.json so the next request (or new window) picks them up.
1995
+ *
1996
+ * Also updates .active-openai-auth when switching between auth-work* files.
1997
+ */
1998
+
1999
+ /** Result of a profile switch attempt. */
2000
+ interface SwitchResult {
2001
+ readonly success: boolean;
2002
+ readonly from?: string;
2003
+ readonly to: string;
2004
+ readonly reason?: string;
2005
+ }
2006
+ /**
2007
+ * Switches the active profile by writing its credentials to auth.json.
2008
+ *
2009
+ * 1. Reads the target profile from profiles.json
2010
+ * 2. Writes its credentials to auth.json (atomic: tmp + rename)
2011
+ * 3. Updates .active-openai-auth if applicable
2012
+ */
2013
+ declare const switchProfile: (options: {
2014
+ readonly targetProfileId: string;
2015
+ readonly profilesPath?: string;
2016
+ readonly authJsonPath?: string;
2017
+ readonly logger?: pino.Logger;
2018
+ }) => Promise<SwitchResult>;
2019
+ /**
2020
+ * Picks the next healthy profile for a provider and switches to it.
2021
+ *
2022
+ * Selection strategy: round-robin among profiles that are NOT the current one.
2023
+ * The caller (plugin.ts) provides health data to skip unhealthy profiles.
2024
+ */
2025
+ declare const switchToNextProfile: (options: {
2026
+ readonly provider: string;
2027
+ readonly currentProfileId?: string;
2028
+ readonly excludeProfileIds?: ReadonlyArray<string>;
2029
+ readonly profilesPath?: string;
2030
+ readonly authJsonPath?: string;
2031
+ readonly logger?: pino.Logger;
2032
+ }) => Promise<SwitchResult>;
2033
+
1992
2034
  /**
1993
2035
  * OpenCode plugin tool definitions for profile management.
1994
2036
  *
@@ -2018,4 +2060,4 @@ interface ProfileToolsOptions {
2018
2060
  */
2019
2061
  declare const createProfileTools: (options?: ProfileToolsOptions) => ProfileTools;
2020
2062
 
2021
- export { ADMISSION_RESULTS, type AdapterRequest, type AdapterResult, type AdmissionConfig, type AdmissionContext, type AdmissionController, type AdmissionDecision, type AdmissionResult, type ApiKeyCredential, type AttemptFn, type AuditCollector, type AuditEvent, type AuditLoggerOptions, type AuthCredential, type AuthResult, type AuthWatcher, type AuthWatcherOptions, type BackoffConfig, BackoffConfigSchema, type BackoffParams, type CircuitBreaker, type CircuitBreakerConfig, type ClassificationResult, type ConcurrencyTrackerApi, type ConfigDiagnostic, type ConfigDiff, type ConfigRef, ConfigValidationError, type ContinuationMode, type CooldownManager, type CorrelationId, DEFAULT_ALPHA, DEFAULT_BACKOFF_BASE_MS, DEFAULT_BACKOFF_JITTER, DEFAULT_BACKOFF_MAX_MS, DEFAULT_BACKOFF_MULTIPLIER, DEFAULT_BACKOFF_PARAMS, DEFAULT_FAILOVER_BUDGET, DEFAULT_RETRY, DEFAULT_RETRY_BUDGET, DEFAULT_TIMEOUT_MS, type DeploymentMode, DualBreaker, EXCLUSION_REASONS, type ErrorClass, ErrorClassSchema, type ExclusionReason, type ExecutionDeps, type ExecutionOrchestrator, type ExecutionProvenance, type ExecutionResult, type FailoverAttempt, type FailoverDeps, type FailoverOrchestrator, type FailoverRegistry, type FailoverResult, HEURISTIC_PATTERNS, INITIAL_HEALTH_SCORE, type InspectResult, type ListTargetsResult, type ModeAdapter, type ModeCapabilities, type NormalizedSignal, type OAuthCredential, type OperatorDeps, type OperatorTools, PROVIDER_PATTERNS, type ProfileEntry, type ProfileStore, type ProfileTools, type ProfileToolsOptions, type ProviderConfigLike, REDACT_PATHS, type RegistrySnapshot, type ReloadResult, type RequestTraceBuffer, type RequestTraceEntry, type RetryDecision, type RetryPolicy, type RoutingEventMap, type RoutingWeights, type SegmentProvenance, type SelectionRecord, type SignalFidelity, type StitchedOutput, type StreamBuffer, type StreamChunk, type StreamStitcher, type SwitcherConfig, SwitcherConfigSchema, TARGET_STATES, TEMPORAL_QUOTA_PATTERN, type TargetActionResult, type TargetConfig, TargetConfigSchema, type TargetEntry, TargetRegistry, type TargetState, type TargetView, addProfile, applyConfigDiff, checkHardRejects, classify, computeBackoffMs, computeConfigDiff, computeCooldownMs, computeScore, createAdmissionController, createAuditCollector, createAuditLogger, createAuthWatcher, createCircuitBreaker, createConcurrencyTracker, createCooldownManager, createExecutionOrchestrator, createFailoverOrchestrator, createLogSubscriber, createModeAdapter, createOperatorTools, createProfileTools, createRegistry, createRequestLogger, createRequestTraceBuffer, createRetryPolicy, createRoutingEventBus, createStreamBuffer, createStreamStitcher, detectDeploymentMode, determineContinuationMode, directSignalFromResponse, disableTarget, discoverTargets, discoverTargetsFromProfiles, drainTarget, extractRetryAfterMs, generateCorrelationId, getExclusionReason, getModeCapabilities, getSignalFidelity, getTargetStateTransition, heuristicSignalFromEvent, inspectRequest, isRetryable, listProfiles, listTargets, loadProfiles, nextProfileId, normalizeLatency, pauseTarget, reloadConfig, removeProfile, resumeTarget, saveProfiles, selectTarget, updateHealthScore, updateLatencyEma, validateBearerToken, validateConfig };
2063
+ export { ADMISSION_RESULTS, type AdapterRequest, type AdapterResult, type AdmissionConfig, type AdmissionContext, type AdmissionController, type AdmissionDecision, type AdmissionResult, type ApiKeyCredential, type AttemptFn, type AuditCollector, type AuditEvent, type AuditLoggerOptions, type AuthCredential, type AuthResult, type AuthWatcher, type AuthWatcherOptions, type BackoffConfig, BackoffConfigSchema, type BackoffParams, type CircuitBreaker, type CircuitBreakerConfig, type ClassificationResult, type ConcurrencyTrackerApi, type ConfigDiagnostic, type ConfigDiff, type ConfigRef, ConfigValidationError, type ContinuationMode, type CooldownManager, type CorrelationId, DEFAULT_ALPHA, DEFAULT_BACKOFF_BASE_MS, DEFAULT_BACKOFF_JITTER, DEFAULT_BACKOFF_MAX_MS, DEFAULT_BACKOFF_MULTIPLIER, DEFAULT_BACKOFF_PARAMS, DEFAULT_FAILOVER_BUDGET, DEFAULT_RETRY, DEFAULT_RETRY_BUDGET, DEFAULT_TIMEOUT_MS, type DeploymentMode, DualBreaker, EXCLUSION_REASONS, type ErrorClass, ErrorClassSchema, type ExclusionReason, type ExecutionDeps, type ExecutionOrchestrator, type ExecutionProvenance, type ExecutionResult, type FailoverAttempt, type FailoverDeps, type FailoverOrchestrator, type FailoverRegistry, type FailoverResult, HEURISTIC_PATTERNS, INITIAL_HEALTH_SCORE, type InspectResult, type ListTargetsResult, type ModeAdapter, type ModeCapabilities, type NormalizedSignal, type OAuthCredential, type OperatorDeps, type OperatorTools, PROVIDER_PATTERNS, type ProfileEntry, type ProfileStore, type ProfileTools, type ProfileToolsOptions, type ProviderConfigLike, REDACT_PATHS, type RegistrySnapshot, type ReloadResult, type RequestTraceBuffer, type RequestTraceEntry, type RetryDecision, type RetryPolicy, type RoutingEventMap, type RoutingWeights, type SegmentProvenance, type SelectionRecord, type SignalFidelity, type StitchedOutput, type StreamBuffer, type StreamChunk, type StreamStitcher, type SwitchResult, type SwitcherConfig, SwitcherConfigSchema, TARGET_STATES, TEMPORAL_QUOTA_PATTERN, type TargetActionResult, type TargetConfig, TargetConfigSchema, type TargetEntry, TargetRegistry, type TargetState, type TargetView, addProfile, applyConfigDiff, checkHardRejects, classify, computeBackoffMs, computeConfigDiff, computeCooldownMs, computeScore, createAdmissionController, createAuditCollector, createAuditLogger, createAuthWatcher, createCircuitBreaker, createConcurrencyTracker, createCooldownManager, createExecutionOrchestrator, createFailoverOrchestrator, createLogSubscriber, createModeAdapter, createOperatorTools, createProfileTools, createRegistry, createRequestLogger, createRequestTraceBuffer, createRetryPolicy, createRoutingEventBus, createStreamBuffer, createStreamStitcher, detectDeploymentMode, determineContinuationMode, directSignalFromResponse, disableTarget, discoverTargets, discoverTargetsFromProfiles, drainTarget, extractRetryAfterMs, generateCorrelationId, getExclusionReason, getModeCapabilities, getSignalFidelity, getTargetStateTransition, heuristicSignalFromEvent, inspectRequest, isRetryable, listProfiles, listTargets, loadProfiles, nextProfileId, normalizeLatency, pauseTarget, reloadConfig, removeProfile, resumeTarget, saveProfiles, selectTarget, switchProfile, switchToNextProfile, updateHealthScore, updateLatencyEma, validateBearerToken, validateConfig };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createRequestLogger, generateCorrelationId } from './chunk-7ITX5623.js';
2
- export { ADMISSION_RESULTS, BackoffConfigSchema, ConfigValidationError, DEFAULT_ALPHA, DEFAULT_BACKOFF_BASE_MS, DEFAULT_BACKOFF_JITTER, DEFAULT_BACKOFF_MAX_MS, DEFAULT_BACKOFF_MULTIPLIER, DEFAULT_BACKOFF_PARAMS, DEFAULT_FAILOVER_BUDGET, DEFAULT_RETRY, DEFAULT_RETRY_BUDGET, DEFAULT_TIMEOUT_MS, DualBreaker, EXCLUSION_REASONS, ErrorClassSchema, INITIAL_HEALTH_SCORE, REDACT_PATHS, SwitcherConfigSchema, TARGET_STATES, TargetConfigSchema, TargetRegistry, addProfile, applyConfigDiff, checkHardRejects, computeBackoffMs, computeConfigDiff, computeCooldownMs, computeScore, createAdmissionController, createAuditLogger, createAuthWatcher, createCircuitBreaker, createConcurrencyTracker, createCooldownManager, createFailoverOrchestrator, createLogSubscriber, createOperatorTools, createProfileTools, createRegistry, createRequestLogger, createRequestTraceBuffer, createRetryPolicy, createRoutingEventBus, disableTarget, discoverTargets, discoverTargetsFromProfiles, drainTarget, generateCorrelationId, getExclusionReason, getTargetStateTransition, inspectRequest, isRetryable, listProfiles, listTargets, loadProfiles, nextProfileId, normalizeLatency, pauseTarget, reloadConfig, removeProfile, resumeTarget, saveProfiles, selectTarget, updateHealthScore, updateLatencyEma, validateConfig } from './chunk-7ITX5623.js';
1
+ import { createRequestLogger, generateCorrelationId } from './chunk-XXH633FY.js';
2
+ export { ADMISSION_RESULTS, BackoffConfigSchema, ConfigValidationError, DEFAULT_ALPHA, DEFAULT_BACKOFF_BASE_MS, DEFAULT_BACKOFF_JITTER, DEFAULT_BACKOFF_MAX_MS, DEFAULT_BACKOFF_MULTIPLIER, DEFAULT_BACKOFF_PARAMS, DEFAULT_FAILOVER_BUDGET, DEFAULT_RETRY, DEFAULT_RETRY_BUDGET, DEFAULT_TIMEOUT_MS, DualBreaker, EXCLUSION_REASONS, ErrorClassSchema, INITIAL_HEALTH_SCORE, REDACT_PATHS, SwitcherConfigSchema, TARGET_STATES, TargetConfigSchema, TargetRegistry, addProfile, applyConfigDiff, checkHardRejects, computeBackoffMs, computeConfigDiff, computeCooldownMs, computeScore, createAdmissionController, createAuditLogger, createAuthWatcher, createCircuitBreaker, createConcurrencyTracker, createCooldownManager, createFailoverOrchestrator, createLogSubscriber, createOperatorTools, createProfileTools, createRegistry, createRequestLogger, createRequestTraceBuffer, createRetryPolicy, createRoutingEventBus, disableTarget, discoverTargets, discoverTargetsFromProfiles, drainTarget, generateCorrelationId, getExclusionReason, getTargetStateTransition, inspectRequest, isRetryable, listProfiles, listTargets, loadProfiles, nextProfileId, normalizeLatency, pauseTarget, reloadConfig, removeProfile, resumeTarget, saveProfiles, selectTarget, switchProfile, switchToNextProfile, updateHealthScore, updateLatencyEma, validateConfig } from './chunk-XXH633FY.js';
3
3
  import { timingSafeEqual } from 'crypto';
4
4
 
5
5
  // src/mode/detection.ts
package/dist/plugin.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var chunkTJ7ZGZHD_cjs = require('./chunk-TJ7ZGZHD.cjs');
5
+ var chunkH72U2MNG_cjs = require('./chunk-H72U2MNG.cjs');
6
6
  var promises = require('fs/promises');
7
7
  var path = require('path');
8
8
  var os = require('os');
@@ -44,21 +44,21 @@ var createStateWriter = (debounceMs = 500) => {
44
44
 
45
45
  // src/plugin.ts
46
46
  var initializeSwitcher = (rawConfig) => {
47
- const config = chunkTJ7ZGZHD_cjs.validateConfig(rawConfig);
48
- const registry = chunkTJ7ZGZHD_cjs.createRegistry(config);
49
- const logger = chunkTJ7ZGZHD_cjs.createAuditLogger();
50
- const eventBus = chunkTJ7ZGZHD_cjs.createRoutingEventBus();
47
+ const config = chunkH72U2MNG_cjs.validateConfig(rawConfig);
48
+ const registry = chunkH72U2MNG_cjs.createRegistry(config);
49
+ const logger = chunkH72U2MNG_cjs.createAuditLogger();
50
+ const eventBus = chunkH72U2MNG_cjs.createRoutingEventBus();
51
51
  const circuitBreakers = /* @__PURE__ */ new Map();
52
52
  for (const target of registry.getAllTargets()) {
53
53
  circuitBreakers.set(
54
54
  target.target_id,
55
- chunkTJ7ZGZHD_cjs.createCircuitBreaker(target.target_id, config.circuit_breaker, eventBus)
55
+ chunkH72U2MNG_cjs.createCircuitBreaker(target.target_id, config.circuit_breaker, eventBus)
56
56
  );
57
57
  }
58
- const concurrency = chunkTJ7ZGZHD_cjs.createConcurrencyTracker(config.concurrency_limit);
59
- const cooldownManager = chunkTJ7ZGZHD_cjs.createCooldownManager(registry, eventBus);
60
- const traceBuffer = chunkTJ7ZGZHD_cjs.createRequestTraceBuffer(100);
61
- chunkTJ7ZGZHD_cjs.createLogSubscriber(eventBus, logger);
58
+ const concurrency = chunkH72U2MNG_cjs.createConcurrencyTracker(config.concurrency_limit);
59
+ const cooldownManager = chunkH72U2MNG_cjs.createCooldownManager(registry, eventBus);
60
+ const traceBuffer = chunkH72U2MNG_cjs.createRequestTraceBuffer(100);
61
+ chunkH72U2MNG_cjs.createLogSubscriber(eventBus, logger);
62
62
  const configRef = {
63
63
  current: () => config,
64
64
  swap: () => {
@@ -94,7 +94,7 @@ var snapshotState = (state) => {
94
94
  };
95
95
  };
96
96
  var server = async (_input) => {
97
- const logger = chunkTJ7ZGZHD_cjs.createAuditLogger({ level: "info" });
97
+ const logger = chunkH72U2MNG_cjs.createAuditLogger({ level: "info" });
98
98
  logger.info("O-Switcher plugin initializing");
99
99
  const state = {};
100
100
  const stateWriter = createStateWriter();
@@ -137,17 +137,17 @@ var server = async (_input) => {
137
137
  rawConfig["targets"] && Array.isArray(rawConfig["targets"]) && rawConfig["targets"].length > 0
138
138
  );
139
139
  if (!hasExplicitTargets) {
140
- const profileStore = await chunkTJ7ZGZHD_cjs.loadProfiles().catch(() => ({}));
140
+ const profileStore = await chunkH72U2MNG_cjs.loadProfiles().catch(() => ({}));
141
141
  const profileKeys = Object.keys(profileStore);
142
142
  if (profileKeys.length > 0) {
143
- const profileTargets = chunkTJ7ZGZHD_cjs.discoverTargetsFromProfiles(profileStore);
143
+ const profileTargets = chunkH72U2MNG_cjs.discoverTargetsFromProfiles(profileStore);
144
144
  rawConfig["targets"] = profileTargets;
145
145
  logger.info(
146
146
  { discovered: profileTargets.length, profiles: profileKeys },
147
147
  "Auto-discovered targets from O-Switcher profiles"
148
148
  );
149
149
  } else if (providerConfig && typeof providerConfig === "object") {
150
- const discoveredTargets = chunkTJ7ZGZHD_cjs.discoverTargets(providerConfig);
150
+ const discoveredTargets = chunkH72U2MNG_cjs.discoverTargets(providerConfig);
151
151
  if (discoveredTargets.length === 0) {
152
152
  logger.warn("No providers found \u2014 running in passthrough mode");
153
153
  return;
@@ -167,7 +167,7 @@ var server = async (_input) => {
167
167
  Object.assign(state, initialized);
168
168
  logger.info({ targets: state.registry?.getAllTargets().length }, "O-Switcher initialized");
169
169
  publishTuiState();
170
- state.authWatcher = chunkTJ7ZGZHD_cjs.createAuthWatcher({ logger });
170
+ state.authWatcher = chunkH72U2MNG_cjs.createAuthWatcher({ logger });
171
171
  await state.authWatcher.start();
172
172
  logger.info("Auth watcher started");
173
173
  } catch (err) {
@@ -182,7 +182,7 @@ var server = async (_input) => {
182
182
  */
183
183
  async "chat.params"(input, output) {
184
184
  if (!state.registry) return;
185
- const requestId = chunkTJ7ZGZHD_cjs.generateCorrelationId();
185
+ const requestId = chunkH72U2MNG_cjs.generateCorrelationId();
186
186
  const targets = state.registry.getAllTargets();
187
187
  const providerId = input.provider?.info?.id ?? input.provider?.info?.name ?? input.model?.providerID ?? void 0;
188
188
  if (!providerId) return;
@@ -239,17 +239,42 @@ var server = async (_input) => {
239
239
  state.registry.recordObservation(target.target_id, 0);
240
240
  state.circuitBreakers?.get(target.target_id)?.recordFailure();
241
241
  publishTuiState();
242
- state.logger?.info(
243
- { target_id: target.target_id, event_type: event.type },
244
- "Recorded failure from session event"
245
- );
242
+ const errorObj = error;
243
+ const statusCode = errorObj.statusCode ?? errorObj.error?.statusCode;
244
+ const errorName = String(errorObj.name ?? errorObj.error ?? "");
245
+ const isRateLimit = statusCode === 429 || statusCode === "429" || errorName.includes("RateLimitError") || errorName.includes("QuotaExceeded");
246
+ if (isRateLimit) {
247
+ state.logger?.info(
248
+ { target_id: target.target_id, statusCode },
249
+ "Rate limit hit \u2014 switching profile"
250
+ );
251
+ chunkH72U2MNG_cjs.switchToNextProfile({
252
+ provider: providerId,
253
+ currentProfileId: target.target_id,
254
+ logger: state.logger
255
+ }).then((result) => {
256
+ if (result.success) {
257
+ state.logger?.info(
258
+ { from: result.from, to: result.to, provider: providerId },
259
+ "Switched to next profile after rate limit"
260
+ );
261
+ }
262
+ }).catch((err) => {
263
+ state.logger?.warn({ err }, "Failed to switch profile");
264
+ });
265
+ } else {
266
+ state.logger?.info(
267
+ { target_id: target.target_id, event_type: event.type },
268
+ "Recorded failure from session event"
269
+ );
270
+ }
246
271
  }
247
272
  }
248
273
  }
249
274
  },
250
275
  tool: {
251
- ...chunkTJ7ZGZHD_cjs.createProfileTools(),
252
- ...chunkTJ7ZGZHD_cjs.createOperatorTools(lazyOperatorDeps)
276
+ ...chunkH72U2MNG_cjs.createProfileTools(),
277
+ ...chunkH72U2MNG_cjs.createOperatorTools(lazyOperatorDeps)
253
278
  }
254
279
  };
255
280
  return hooks;
package/dist/plugin.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createAuditLogger, createOperatorTools, createProfileTools, generateCorrelationId, loadProfiles, discoverTargetsFromProfiles, discoverTargets, createAuthWatcher, validateConfig, createRegistry, createRoutingEventBus, createCircuitBreaker, createConcurrencyTracker, createCooldownManager, createRequestTraceBuffer, createLogSubscriber } from './chunk-7ITX5623.js';
1
+ import { createAuditLogger, createOperatorTools, createProfileTools, switchToNextProfile, generateCorrelationId, loadProfiles, discoverTargetsFromProfiles, discoverTargets, createAuthWatcher, validateConfig, createRegistry, createRoutingEventBus, createCircuitBreaker, createConcurrencyTracker, createCooldownManager, createRequestTraceBuffer, createLogSubscriber } from './chunk-XXH633FY.js';
2
2
  import { mkdir, writeFile, rename } from 'fs/promises';
3
3
  import { join, dirname } from 'path';
4
4
  import { homedir } from 'os';
@@ -235,10 +235,35 @@ var server = async (_input) => {
235
235
  state.registry.recordObservation(target.target_id, 0);
236
236
  state.circuitBreakers?.get(target.target_id)?.recordFailure();
237
237
  publishTuiState();
238
- state.logger?.info(
239
- { target_id: target.target_id, event_type: event.type },
240
- "Recorded failure from session event"
241
- );
238
+ const errorObj = error;
239
+ const statusCode = errorObj.statusCode ?? errorObj.error?.statusCode;
240
+ const errorName = String(errorObj.name ?? errorObj.error ?? "");
241
+ const isRateLimit = statusCode === 429 || statusCode === "429" || errorName.includes("RateLimitError") || errorName.includes("QuotaExceeded");
242
+ if (isRateLimit) {
243
+ state.logger?.info(
244
+ { target_id: target.target_id, statusCode },
245
+ "Rate limit hit \u2014 switching profile"
246
+ );
247
+ switchToNextProfile({
248
+ provider: providerId,
249
+ currentProfileId: target.target_id,
250
+ logger: state.logger
251
+ }).then((result) => {
252
+ if (result.success) {
253
+ state.logger?.info(
254
+ { from: result.from, to: result.to, provider: providerId },
255
+ "Switched to next profile after rate limit"
256
+ );
257
+ }
258
+ }).catch((err) => {
259
+ state.logger?.warn({ err }, "Failed to switch profile");
260
+ });
261
+ } else {
262
+ state.logger?.info(
263
+ { target_id: target.target_id, event_type: event.type },
264
+ "Recorded failure from session event"
265
+ );
266
+ }
242
267
  }
243
268
  }
244
269
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@albinocrabs/o-switcher",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Seamless OpenRouter profile rotation for OpenCode — buy multiple subscriptions, use as one pool",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
package/src/tui.tsx CHANGED
@@ -1,3 +1,4 @@
1
+ /** @jsxImportSource @opentui/solid */
1
2
  /**
2
3
  * O-Switcher TUI plugin for OpenCode.
3
4
  *
@@ -102,7 +103,7 @@ const SidebarFooter = () => {
102
103
  };
103
104
 
104
105
  /** Full status dashboard route. */
105
- const StatusDashboard = () => {
106
+ const StatusDashboard = (props: { api: TuiPluginApi }) => {
106
107
  const state = useTuiState();
107
108
 
108
109
  const content = (): string => {
@@ -166,11 +167,19 @@ const StatusDashboard = () => {
166
167
  const age = Date.now() - s.updated_at;
167
168
  const ageSec = Math.round(age / 1000);
168
169
  lines.push(` Updated ${ageSec}s ago`);
170
+ lines.push('');
171
+ lines.push(' Press Esc or q to go back');
169
172
 
170
173
  return lines.join('\n');
171
174
  };
172
175
 
173
- return <text wrapMode="none">{content()}</text>;
176
+ const onKeyDown = (e: { name: string }) => {
177
+ if (e.name === 'escape' || e.name === 'q') {
178
+ props.api.route.navigate('home');
179
+ }
180
+ };
181
+
182
+ return <box focused onKeyDown={onKeyDown}><text wrapMode="none">{content()}</text></box>;
174
183
  };
175
184
 
176
185
  // ── Plugin entry point ────────────────────────────────────────────
@@ -189,7 +198,7 @@ const tui = async (api: TuiPluginApi) => {
189
198
  api.route.register([
190
199
  {
191
200
  name: 'o-switcher-status',
192
- render: () => <StatusDashboard />,
201
+ render: () => <StatusDashboard api={api} />,
193
202
  },
194
203
  ]);
195
204
 
@@ -210,5 +219,8 @@ const tui = async (api: TuiPluginApi) => {
210
219
  ]);
211
220
  };
212
221
 
213
- const tuiModule: TuiPluginModule = { tui };
222
+ const tuiModule: TuiPluginModule & { id: string } = {
223
+ id: 'o-switcher',
224
+ tui,
225
+ };
214
226
  export default tuiModule;