@pikoloo/codex-proxy 1.0.7 → 1.1.0

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/public/js/app.js CHANGED
@@ -10,7 +10,6 @@ document.addEventListener('alpine:init', () => {
10
10
  version: '1.0.7',
11
11
  connectionStatus: 'connecting',
12
12
  activeTab: initialTab(),
13
- sidebarOpen: window.innerWidth >= 1024,
14
13
  loading: false,
15
14
  toast: null,
16
15
  currentTime: '',
@@ -49,10 +48,7 @@ document.addEventListener('alpine:init', () => {
49
48
  reasoningLevelOptions: [],
50
49
  modelMappingSaving: null,
51
50
  reasoningMappingSaving: null,
52
- accountStrategy: 'sticky',
53
- multiAccountRotationEnabled: false,
54
51
  haikuModelSaving: false,
55
- strategySaving: false,
56
52
  configureClaudeOnStartup: false,
57
53
  claudeProxyConfiguring: false,
58
54
  claudeProxyStartupSaving: false,
@@ -73,32 +69,72 @@ document.addEventListener('alpine:init', () => {
73
69
 
74
70
  testPrompt: 'Say hello',
75
71
  testResponse: '',
72
+ testStatus: 'idle',
73
+ testError: '',
74
+ testMeta: null,
76
75
  testing: false,
77
76
 
78
77
  haikuTestPrompt: 'Say hello',
79
78
  haikuTestResponse: '',
79
+ haikuTestStatus: 'idle',
80
+ haikuTestError: '',
81
+ haikuTestMeta: null,
80
82
  haikuTesting: false,
81
83
 
82
84
  haikuModelLabel() {
83
85
  return this.modelOptionName(this.modelMappings?.haiku || 'gpt-5.4-mini');
84
86
  },
85
87
 
88
+ get testStatusText() {
89
+ const labels = {
90
+ idle: 'Ready',
91
+ running: 'Sending request',
92
+ success: 'Response received',
93
+ error: 'Request failed'
94
+ };
95
+ return labels[this.testStatus] || 'Ready';
96
+ },
97
+
98
+ get haikuTestStatusText() {
99
+ const labels = {
100
+ idle: 'Ready',
101
+ running: 'Sending Haiku request',
102
+ success: 'Response received',
103
+ error: 'Request failed'
104
+ };
105
+ return labels[this.haikuTestStatus] || 'Ready';
106
+ },
107
+
86
108
  async testHaikuChat() {
87
109
  if (!this.haikuTestPrompt.trim()) return;
110
+ const startedAt = Date.now();
88
111
  this.haikuTesting = true;
89
112
  this.haikuTestResponse = '';
90
- const { ok, data } = await this.api('/v1/chat/completions', {
91
- method: 'POST',
92
- body: JSON.stringify({
93
- model: 'claude-haiku-4',
94
- messages: [{ role: 'user', content: this.haikuTestPrompt }]
95
- })
96
- });
97
- this.haikuTesting = false;
98
- if (ok && data.choices) {
99
- this.haikuTestResponse = data.choices[0].message.content;
100
- } else {
101
- this.haikuTestResponse = data?.error?.message || 'Request failed';
113
+ this.haikuTestStatus = 'running';
114
+ this.haikuTestError = '';
115
+ this.haikuTestMeta = null;
116
+
117
+ try {
118
+ const { ok, data, error } = await this.api('/v1/chat/completions', {
119
+ method: 'POST',
120
+ body: JSON.stringify({
121
+ model: 'claude-haiku-4',
122
+ messages: [{ role: 'user', content: this.haikuTestPrompt }]
123
+ })
124
+ });
125
+ const durationMs = Date.now() - startedAt;
126
+ this.haikuTestMeta = { durationMs, usage: data?.usage || null };
127
+
128
+ if (ok && data.choices) {
129
+ this.haikuTestResponse = data.choices[0].message.content;
130
+ this.haikuTestStatus = 'success';
131
+ } else {
132
+ this.haikuTestError = data?.error?.message || error || 'Request failed';
133
+ this.haikuTestResponse = this.haikuTestError;
134
+ this.haikuTestStatus = 'error';
135
+ }
136
+ } finally {
137
+ this.haikuTesting = false;
102
138
  }
103
139
  },
104
140
 
@@ -109,6 +145,7 @@ document.addEventListener('alpine:init', () => {
109
145
  logSearchQuery: '',
110
146
  logFilters: { INFO: true, SUCCESS: true, WARN: true, ERROR: true, DEBUG: false },
111
147
  logEventSource: null,
148
+ logStreamStatus: 'connecting',
112
149
 
113
150
  get filteredLogs() {
114
151
  const query = this.logSearchQuery.trim().toLowerCase();
@@ -119,6 +156,13 @@ document.addEventListener('alpine:init', () => {
119
156
  });
120
157
  },
121
158
 
159
+ get logLevelCounts() {
160
+ return this.logs.reduce((counts, log) => {
161
+ counts[log.level] = (counts[log.level] || 0) + 1;
162
+ return counts;
163
+ }, { INFO: 0, SUCCESS: 0, WARN: 0, ERROR: 0, DEBUG: 0 });
164
+ },
165
+
122
166
  get filteredAccounts() {
123
167
  if (!this.searchQuery) return this.accounts;
124
168
  const q = this.searchQuery.toLowerCase();
@@ -159,14 +203,9 @@ document.addEventListener('alpine:init', () => {
159
203
  this.startLogStream();
160
204
  this.loadModelMappingsSetting();
161
205
  this.loadHaikuModelSetting();
162
- this.loadAccountStrategySetting();
163
206
  this.loadClaudeProxySetting();
164
207
  this.loadMetrics();
165
208
 
166
- window.addEventListener('resize', () => {
167
- this.sidebarOpen = window.innerWidth >= 1024;
168
- });
169
-
170
209
  window.addEventListener('message', (event) => {
171
210
  if (event.data && event.data.type === 'oauth-success') {
172
211
  this.showToast(`Account ${event.data.email} added!`, 'success');
@@ -195,9 +234,6 @@ document.addEventListener('alpine:init', () => {
195
234
  nextUrl.hash = '';
196
235
  }
197
236
  window.history.replaceState({}, '', nextUrl);
198
- if (window.innerWidth < 1024) {
199
- this.sidebarOpen = false;
200
- }
201
237
  },
202
238
 
203
239
  async api(endpoint, options = {}) {
@@ -305,6 +341,15 @@ document.addEventListener('alpine:init', () => {
305
341
  return `${number}ms`;
306
342
  },
307
343
 
344
+ formatUsageSummary(usage) {
345
+ if (!usage) return '';
346
+ const input = Number(usage.prompt_tokens ?? usage.input_tokens) || 0;
347
+ const output = Number(usage.completion_tokens ?? usage.output_tokens) || 0;
348
+ const total = Number(usage.total_tokens ?? (input + output)) || 0;
349
+ if (total <= 0) return '';
350
+ return `${this.formatTokenCount(total)} tokens (${this.formatTokenCount(input)} in, ${this.formatTokenCount(output)} out)`;
351
+ },
352
+
308
353
  formatBytes(value) {
309
354
  const number = Number(value) || 0;
310
355
  if (number >= 1024 * 1024) return `${(number / (1024 * 1024)).toFixed(1)} MB`;
@@ -582,20 +627,34 @@ document.addEventListener('alpine:init', () => {
582
627
 
583
628
  async testChat() {
584
629
  if (!this.testPrompt.trim()) return;
630
+ const startedAt = Date.now();
585
631
  this.testing = true;
586
632
  this.testResponse = '';
587
- const { ok, data } = await this.api('/v1/chat/completions', {
588
- method: 'POST',
589
- body: JSON.stringify({
590
- model: 'gpt-5.5',
591
- messages: [{ role: 'user', content: this.testPrompt }]
592
- })
593
- });
594
- this.testing = false;
595
- if (ok && data.choices) {
596
- this.testResponse = data.choices[0].message.content;
597
- } else {
598
- this.testResponse = data?.error?.message || 'Request failed';
633
+ this.testStatus = 'running';
634
+ this.testError = '';
635
+ this.testMeta = null;
636
+
637
+ try {
638
+ const { ok, data, error } = await this.api('/v1/chat/completions', {
639
+ method: 'POST',
640
+ body: JSON.stringify({
641
+ model: 'gpt-5.5',
642
+ messages: [{ role: 'user', content: this.testPrompt }]
643
+ })
644
+ });
645
+ const durationMs = Date.now() - startedAt;
646
+ this.testMeta = { durationMs, usage: data?.usage || null };
647
+
648
+ if (ok && data.choices) {
649
+ this.testResponse = data.choices[0].message.content;
650
+ this.testStatus = 'success';
651
+ } else {
652
+ this.testError = data?.error?.message || error || 'Request failed';
653
+ this.testResponse = this.testError;
654
+ this.testStatus = 'error';
655
+ }
656
+ } finally {
657
+ this.testing = false;
599
658
  }
600
659
  },
601
660
 
@@ -725,31 +784,6 @@ document.addEventListener('alpine:init', () => {
725
784
  }
726
785
  },
727
786
 
728
- async loadAccountStrategySetting() {
729
- const { ok, data } = await this.api('/settings/account-strategy');
730
- if (ok && data?.accountStrategy) {
731
- this.accountStrategy = data.accountStrategy;
732
- this.multiAccountRotationEnabled = data.rotationEnabled === true;
733
- }
734
- },
735
-
736
- async setAccountStrategy(strategy) {
737
- if (!this.multiAccountRotationEnabled || this.strategySaving || this.accountStrategy === strategy) return;
738
- this.strategySaving = true;
739
- const { ok, data } = await this.api('/settings/account-strategy', {
740
- method: 'POST',
741
- body: JSON.stringify({ accountStrategy: strategy })
742
- });
743
- this.strategySaving = false;
744
- if (ok && data?.accountStrategy) {
745
- this.accountStrategy = data.accountStrategy;
746
- this.multiAccountRotationEnabled = data.rotationEnabled === true;
747
- this.showToast(`Account strategy set to ${data.accountStrategy === 'sticky' ? 'Sticky' : 'Round-Robin'}`, 'success');
748
- } else {
749
- this.showToast(data?.error || 'Failed to update strategy', 'error');
750
- }
751
- },
752
-
753
787
  async loadClaudeProxySetting() {
754
788
  const { ok, data } = await this.api('/settings/claude-proxy');
755
789
  if (ok && typeof data?.configureClaudeOnStartup === 'boolean') {
@@ -802,11 +836,16 @@ document.addEventListener('alpine:init', () => {
802
836
 
803
837
  startLogStream() {
804
838
  if (this.logEventSource) this.logEventSource.close();
805
-
839
+
840
+ this.logStreamStatus = 'connecting';
806
841
  this.logEventSource = new EventSource('/api/logs/stream?history=true');
842
+ this.logEventSource.onopen = () => {
843
+ this.logStreamStatus = 'connected';
844
+ };
807
845
  this.logEventSource.onmessage = (event) => {
808
846
  try {
809
847
  const log = JSON.parse(event.data);
848
+ this.logStreamStatus = 'connected';
810
849
  this.logs.unshift(log);
811
850
 
812
851
  if (this.logs.length > 500) {
@@ -816,6 +855,8 @@ document.addEventListener('alpine:init', () => {
816
855
  };
817
856
 
818
857
  this.logEventSource.onerror = () => {
858
+ this.logStreamStatus = 'disconnected';
859
+ if (this.logEventSource) this.logEventSource.close();
819
860
  setTimeout(() => this.startLogStream(), 3000);
820
861
  };
821
862
  },
@@ -833,6 +874,22 @@ document.addEventListener('alpine:init', () => {
833
874
  return message;
834
875
  },
835
876
 
877
+ formatLogTime(timestamp) {
878
+ if (!timestamp) return '--:--:--';
879
+ const date = new Date(timestamp);
880
+ if (Number.isNaN(date.getTime())) return '--:--:--';
881
+ return date.toLocaleTimeString([], { hour12: false });
882
+ },
883
+
884
+ logStreamStatusText() {
885
+ const labels = {
886
+ connecting: 'Connecting',
887
+ connected: 'Live',
888
+ disconnected: 'Reconnecting'
889
+ };
890
+ return labels[this.logStreamStatus] || 'Unknown';
891
+ },
892
+
836
893
  getLogDetails(message) {
837
894
  if (!message) return null;
838
895
  const details = {};
@@ -4,20 +4,20 @@ import {
4
4
  clearInvalid,
5
5
  isAllRateLimited,
6
6
  getMinWaitTimeMs,
7
- clearExpiredLimits
7
+ clearExpiredLimits,
8
+ isAccountCoolingDown
8
9
  } from './rate-limits.js';
9
10
 
10
- import { createStrategy, STRATEGIES } from './strategies/index.js';
11
+ const MAX_WAIT_BEFORE_ERROR_MS = 120000;
11
12
 
12
13
  export class AccountRotator {
13
- constructor(accountManager, strategyName = 'sticky') {
14
+ constructor(accountManager) {
14
15
  this.accountManager = accountManager;
15
- this.strategy = createStrategy(strategyName);
16
16
  }
17
17
 
18
- selectAccount(modelId, options = {}) {
18
+ selectAccount(modelId) {
19
19
  const { accounts } = this.accountManager.listAccounts();
20
- return this.strategy.selectAccount(accounts, modelId, options);
20
+ return selectAccount(accounts, modelId);
21
21
  }
22
22
 
23
23
  markRateLimited(email, resetMs, modelId) {
@@ -48,23 +48,11 @@ export class AccountRotator {
48
48
  return getMinWaitTimeMs(accounts, modelId);
49
49
  }
50
50
 
51
- notifySuccess(account, modelId) {
52
- if (this.strategy.notifySuccess) {
53
- this.strategy.notifySuccess(account, modelId);
54
- }
55
- }
51
+ notifySuccess(account, modelId) {}
56
52
 
57
- notifyRateLimit(account, modelId) {
58
- if (this.strategy.notifyRateLimit) {
59
- this.strategy.notifyRateLimit(account, modelId);
60
- }
61
- }
53
+ notifyRateLimit(account, modelId) {}
62
54
 
63
- notifyFailure(account, modelId) {
64
- if (this.strategy.notifyFailure) {
65
- this.strategy.notifyFailure(account, modelId);
66
- }
67
- }
55
+ notifyFailure(account, modelId) {}
68
56
 
69
57
  clearExpiredLimits() {
70
58
  const { accounts } = this.accountManager.listAccounts();
@@ -72,18 +60,67 @@ export class AccountRotator {
72
60
  this.accountManager.save();
73
61
  }
74
62
 
75
- getStrategyName() {
76
- return this.strategy.name;
63
+ }
64
+
65
+ function selectAccount(accounts, modelId) {
66
+ if (!accounts || accounts.length === 0) {
67
+ return { account: null, index: 0, waitMs: 0 };
68
+ }
69
+
70
+ const activeIndex = accounts.findIndex((account) => account.isActive);
71
+ const startIndex = activeIndex >= 0 ? activeIndex : 0;
72
+
73
+ for (let offset = 0; offset < accounts.length; offset++) {
74
+ const index = (startIndex + offset) % accounts.length;
75
+ const account = accounts[index];
76
+
77
+ if (isAccountUsable(account, modelId)) {
78
+ account.lastUsed = Date.now();
79
+ return { account, index, waitMs: 0 };
80
+ }
77
81
  }
78
82
 
79
- getStrategyLabel() {
80
- return this.strategy.label;
83
+ const waitMs = getAccountWaitMs(accounts[startIndex], modelId);
84
+ if (waitMs > 0 && waitMs <= MAX_WAIT_BEFORE_ERROR_MS) {
85
+ return { account: null, index: startIndex, waitMs };
81
86
  }
87
+
88
+ return { account: null, index: startIndex, waitMs: 0 };
89
+ }
90
+
91
+ function isAccountUsable(account, modelId) {
92
+ if (!account) return false;
93
+ if (account.isInvalid) return false;
94
+ if (account.enabled === false) return false;
95
+ if (isAccountCoolingDown(account)) return false;
96
+
97
+ const waitMs = getModelRateLimitWaitMs(account, modelId);
98
+ return waitMs === 0;
99
+ }
100
+
101
+ function getAccountWaitMs(account, modelId) {
102
+ if (!account) return 0;
103
+ if (account.isInvalid) return 0;
104
+ if (account.enabled === false) return 0;
105
+ if (isAccountCoolingDown(account)) return 0;
106
+
107
+ return getModelRateLimitWaitMs(account, modelId);
108
+ }
109
+
110
+ function getModelRateLimitWaitMs(account, modelId) {
111
+ if (!modelId || !account?.modelRateLimits?.[modelId]) {
112
+ return 0;
113
+ }
114
+
115
+ const limit = account.modelRateLimits[modelId];
116
+ if (!limit?.isRateLimited || !limit.resetTime || limit.resetTime <= Date.now()) {
117
+ return 0;
118
+ }
119
+
120
+ return limit.resetTime - Date.now();
82
121
  }
83
122
 
84
123
  export {
85
- createStrategy,
86
- STRATEGIES,
87
124
  markRateLimited,
88
125
  markInvalid,
89
126
  clearInvalid,
package/src/index.js CHANGED
@@ -14,7 +14,7 @@ startServer({ port: PORT, host: HOST });
14
14
 
15
15
  console.log(`
16
16
  ╔══════════════════════════════════════════════════════════════╗
17
- ║ Codex Claude Proxy v1.0.7
17
+ ║ Codex Claude Proxy v1.1.0 ║
18
18
  ║ (Direct API Mode) ║
19
19
  ╠══════════════════════════════════════════════════════════════╣
20
20
  ║ Server: http://${HOST}:${PORT} ║
@@ -21,8 +21,6 @@ import {
21
21
  handleGetKiloModels,
22
22
  handleGetModelMappings,
23
23
  handleSetModelMappings,
24
- handleGetAccountStrategy,
25
- handleSetAccountStrategy,
26
24
  handleGetClaudeProxySetting,
27
25
  handleSetClaudeProxySetting
28
26
  } from './settings-route.js';
@@ -77,8 +75,6 @@ export function registerApiRoutes(app, { port }) {
77
75
  app.get('/settings/kilo-models', handleGetKiloModels);
78
76
  app.get('/settings/model-mappings', handleGetModelMappings);
79
77
  app.post('/settings/model-mappings', handleSetModelMappings);
80
- app.get('/settings/account-strategy', handleGetAccountStrategy);
81
- app.post('/settings/account-strategy', handleSetAccountStrategy);
82
78
  app.get('/settings/claude-proxy', handleGetClaudeProxySetting);
83
79
  app.post('/settings/claude-proxy', handleSetClaudeProxySetting);
84
80
 
@@ -77,7 +77,7 @@ export async function handleChatCompletion(req, res) {
77
77
  : await sendMessage(anthropicRequest, creds.accessToken, creds.accountId);
78
78
 
79
79
  const duration = Date.now() - startTime;
80
- logger.response(200, { model: upstreamModel, tokens: response.usage?.output_tokens || 0, duration });
80
+ logger.response(200, { model: upstreamModel, usage: response.usage, duration });
81
81
  recordChatMetric({
82
82
  body,
83
83
  requestedModel,
@@ -5,8 +5,8 @@ import { sendAuthError, getCredentialsOrError, getCredentialsForAccount } from '
5
5
  import { initSSEResponse, pipeSSEStream, handleStreamError } from '../middleware/sse.js';
6
6
  import { logger } from '../utils/logger.js';
7
7
  import { AccountRotator } from '../account-rotation/index.js';
8
- import { listAccounts, getActiveAccount, save } from '../account-manager.js';
9
- import { getServerSettings, isMultiAccountRotationEnabled } from '../server-settings.js';
8
+ import { listAccounts, save } from '../account-manager.js';
9
+ import { isMultiAccountRotationEnabled } from '../server-settings.js';
10
10
  import { recordUsageEventSafe, tapUsageEventStream } from '../usage-metrics.js';
11
11
 
12
12
  const MAX_RETRIES = 5;
@@ -14,20 +14,14 @@ const MAX_WAIT_BEFORE_ERROR_MS = 120000;
14
14
  const SHORT_RATE_LIMIT_THRESHOLD_MS = 5000;
15
15
 
16
16
  let accountRotator = null;
17
- let currentStrategy = null;
18
17
 
19
18
  function getAccountRotator() {
20
- const settings = getServerSettings();
21
- const strategy = settings.accountStrategy || 'sticky';
22
-
23
- if (!accountRotator || currentStrategy !== strategy) {
19
+ if (!accountRotator) {
24
20
  accountRotator = new AccountRotator({
25
21
  listAccounts,
26
- save,
27
- getActiveAccount
28
- }, strategy);
29
- currentStrategy = strategy;
30
- logger.info(`[Messages] Account strategy: ${strategy}`);
22
+ save
23
+ });
24
+ logger.info('[Messages] Account rotation enabled');
31
25
  }
32
26
  return accountRotator;
33
27
  }
@@ -246,7 +240,9 @@ export async function handleMessages(req, res) {
246
240
  async function _streamDirectWithRotation(res, anthropicRequest, creds, responseModel, startTime, rotator) {
247
241
  initSSEResponse(res);
248
242
  const sourceStream = sendMessageStream(anthropicRequest, creds.accessToken, creds.accountId, rotator, creds.email);
243
+ let finalUsage = null;
249
244
  const stream = tapUsageEventStream(sourceStream, (usage) => {
245
+ finalUsage = usage;
250
246
  recordMessageMetric({
251
247
  body: anthropicRequest,
252
248
  endpoint: '/v1/messages',
@@ -261,13 +257,13 @@ async function _streamDirectWithRotation(res, anthropicRequest, creds, responseM
261
257
  });
262
258
  });
263
259
  await pipeSSEStream(res, stream);
264
- logger.response(200, { model: anthropicRequest.model, duration: Date.now() - startTime });
260
+ logger.response(200, { model: anthropicRequest.model, usage: finalUsage, duration: Date.now() - startTime });
265
261
  }
266
262
 
267
263
  async function _sendDirectWithRotation(res, anthropicRequest, creds, responseModel, startTime, rotator) {
268
264
  const response = await sendMessage(anthropicRequest, creds.accessToken, creds.accountId);
269
265
  const duration = Date.now() - startTime;
270
- logger.response(200, { model: anthropicRequest.model, tokens: response.usage?.output_tokens || 0, duration });
266
+ logger.response(200, { model: anthropicRequest.model, usage: response.usage, duration });
271
267
  recordMessageMetric({
272
268
  body: anthropicRequest,
273
269
  endpoint: '/v1/messages',
@@ -287,7 +283,9 @@ async function _sendDirectWithRotation(res, anthropicRequest, creds, responseMod
287
283
  async function _streamKilo(res, anthropicRequest, kiloTarget, responseModel, startTime) {
288
284
  initSSEResponse(res);
289
285
  const sourceStream = sendKiloMessageStream(anthropicRequest, kiloTarget);
286
+ let finalUsage = null;
290
287
  const stream = tapUsageEventStream(sourceStream, (usage) => {
288
+ finalUsage = usage;
291
289
  recordMessageMetric({
292
290
  body: anthropicRequest,
293
291
  endpoint: '/v1/messages',
@@ -302,13 +300,13 @@ async function _streamKilo(res, anthropicRequest, kiloTarget, responseModel, sta
302
300
  });
303
301
  });
304
302
  await pipeSSEStream(res, stream);
305
- logger.response(200, { model: kiloTarget, duration: Date.now() - startTime });
303
+ logger.response(200, { model: kiloTarget, usage: finalUsage, duration: Date.now() - startTime });
306
304
  }
307
305
 
308
306
  async function _sendKilo(res, anthropicRequest, kiloTarget, responseModel, startTime) {
309
307
  const response = await sendKiloMessage(anthropicRequest, kiloTarget);
310
308
  const duration = Date.now() - startTime;
311
- logger.response(200, { model: kiloTarget, tokens: response.usage?.output_tokens || 0, duration });
309
+ logger.response(200, { model: kiloTarget, usage: response.usage, duration });
312
310
  recordMessageMetric({
313
311
  body: anthropicRequest,
314
312
  endpoint: '/v1/messages',
@@ -3,14 +3,12 @@
3
3
  * Handles server settings endpoints:
4
4
  * GET /settings/haiku-model
5
5
  * POST /settings/haiku-model
6
- * GET /settings/account-strategy
7
- * POST /settings/account-strategy
8
6
  * GET /settings/claude-proxy
9
7
  * POST /settings/claude-proxy
10
8
  * GET /settings/kilo-models
11
9
  */
12
10
 
13
- import { getServerSettings, isMultiAccountRotationEnabled, setServerSettings } from '../server-settings.js';
11
+ import { getServerSettings, setServerSettings } from '../server-settings.js';
14
12
  import { fetchFreeModels } from '../kilo-models.js';
15
13
  import {
16
14
  CLAUDE_MODEL_ALIASES,
@@ -23,7 +21,6 @@ import {
23
21
  normalizeReasoningMappings
24
22
  } from '../model-mapper.js';
25
23
 
26
- const VALID_STRATEGIES = ['sticky', 'round-robin'];
27
24
  const VALID_OPENAI_MODEL_IDS = new Set(OPENAI_MODEL_OPTIONS.map((model) => model.id));
28
25
  const VALID_REASONING_LEVEL_IDS = new Set(REASONING_LEVEL_OPTIONS.map((level) => level.id));
29
26
 
@@ -208,41 +205,6 @@ export function handleSetModelMappings(req, res) {
208
205
  res.json(modelMappingsPayload(nextSettings.modelMappings, nextSettings.reasoningMappings));
209
206
  }
210
207
 
211
- /**
212
- * GET /settings/account-strategy
213
- * Returns the current account selection strategy.
214
- */
215
- export function handleGetAccountStrategy(req, res) {
216
- const settings = getServerSettings();
217
- res.json({
218
- success: true,
219
- accountStrategy: settings.accountStrategy,
220
- rotationEnabled: isMultiAccountRotationEnabled()
221
- });
222
- }
223
-
224
- /**
225
- * POST /settings/account-strategy
226
- * Updates the account selection strategy.
227
- */
228
- export function handleSetAccountStrategy(req, res) {
229
- const { accountStrategy } = req.body || {};
230
-
231
- if (!VALID_STRATEGIES.includes(accountStrategy)) {
232
- return res.status(400).json({
233
- success: false,
234
- error: `Invalid accountStrategy. Use one of: ${VALID_STRATEGIES.join(', ')}`
235
- });
236
- }
237
-
238
- const settings = setServerSettings({ accountStrategy });
239
- res.json({
240
- success: true,
241
- accountStrategy: settings.accountStrategy,
242
- rotationEnabled: isMultiAccountRotationEnabled()
243
- });
244
- }
245
-
246
208
  /**
247
209
  * GET /settings/claude-proxy
248
210
  * Returns Claude proxy configuration preferences.
@@ -282,8 +244,6 @@ export default {
282
244
  handleGetKiloModels,
283
245
  handleGetModelMappings,
284
246
  handleSetModelMappings,
285
- handleGetAccountStrategy,
286
- handleSetAccountStrategy,
287
247
  handleGetClaudeProxySetting,
288
248
  handleSetClaudeProxySetting
289
249
  };