@cartanova/qgrid-cli 2.4.2 → 2.4.4
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/bundle/dist/application/qgrid/qgrid.frame.js +13 -17
- package/bundle/dist/application/qgrid/token-subscriber.js +23 -9
- package/bundle/dist/application/qgrid/token-trigger-setup.js +4 -4
- package/bundle/dist/application/qgrid/tool-emulation.js +1 -1
- package/bundle/dist/application/sonamu.generated.js +5 -3
- package/bundle/dist/application/sonamu.generated.sso.js +3 -2
- package/bundle/dist/application/token/token.model.js +8 -3
- package/bundle/dist/application/token/token.types.js +8 -3
- package/bundle/dist/i18n/sd.generated.js +2 -1
- package/bundle/dist/index.js +3 -6
- package/bundle/dist/migrations/20260521134025_alter_request_log_steps_alter1.js +3 -9
- package/bundle/dist/migrations/20260706120000_alter_request_logs_add_image_cost.js +1 -1
- package/bundle/dist/migrations/20260710090000_alter_tokens_add_weight.js +41 -0
- package/bundle/dist/server-bootstrap.js +17 -0
- package/bundle/dist/sonamu.config.js +2 -10
- package/bundle/dist/startup-migrations.js +21 -0
- package/bundle/dist/testing/global.js +93 -5
- package/bundle/dist/testing/test-database-name.js +22 -0
- package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +24 -27
- package/bundle/dist/utils/providers/common/model-cost.js +22 -1
- package/bundle/dist/utils/providers/common/smooth-weighted-round-robin.js +43 -0
- package/bundle/dist/utils/providers/openai/codex-worker.js +19 -2
- package/bundle/dist/utils/providers/openai/openai-dispatcher.js +53 -40
- package/bundle/src/application/qgrid/qgrid.frame.test.ts +36 -14
- package/bundle/src/application/qgrid/qgrid.frame.ts +22 -25
- package/bundle/src/application/qgrid/token-subscriber.test.ts +102 -0
- package/bundle/src/application/qgrid/token-subscriber.ts +37 -6
- package/bundle/src/application/qgrid/token-trigger-setup.test.ts +9 -0
- package/bundle/src/application/qgrid/token-trigger-setup.ts +2 -2
- package/bundle/src/application/qgrid/token-weight-migration.test.ts +42 -0
- package/bundle/src/application/qgrid/tool-emulation.ts +6 -1
- package/bundle/src/application/sonamu.generated.http +4 -2
- package/bundle/src/application/sonamu.generated.sso.ts +1 -0
- package/bundle/src/application/sonamu.generated.ts +10 -1
- package/bundle/src/application/token/token.entity.json +17 -1
- package/bundle/src/application/token/token.model.test.ts +43 -0
- package/bundle/src/application/token/token.model.ts +18 -2
- package/bundle/src/application/token/token.types.test.ts +11 -0
- package/bundle/src/application/token/token.types.ts +3 -0
- package/bundle/src/i18n/sd.generated.ts +1 -0
- package/bundle/src/index.ts +2 -5
- package/bundle/src/migrations/20260521134025_alter_request_log_steps_alter1.ts +4 -8
- package/bundle/src/migrations/20260706120000_alter_request_logs_add_image_cost.ts +1 -1
- package/bundle/src/migrations/20260710090000_alter_tokens_add_weight.ts +38 -0
- package/bundle/src/server-bootstrap.test.ts +39 -0
- package/bundle/src/server-bootstrap.ts +21 -0
- package/bundle/src/sonamu.config.ts +3 -12
- package/bundle/src/startup-migrations.test.ts +25 -0
- package/bundle/src/startup-migrations.ts +20 -0
- package/bundle/src/testing/global.test.ts +130 -0
- package/bundle/src/testing/global.ts +143 -13
- package/bundle/src/testing/test-database-name.test.ts +38 -0
- package/bundle/src/testing/test-database-name.ts +21 -0
- package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.test.ts +75 -45
- package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.ts +23 -29
- package/bundle/src/utils/providers/common/model-cost.test.ts +54 -0
- package/bundle/src/utils/providers/common/model-cost.ts +31 -0
- package/bundle/src/utils/providers/common/provider-dispatcher.ts +1 -1
- package/bundle/src/utils/providers/common/smooth-weighted-round-robin.test.ts +73 -0
- package/bundle/src/utils/providers/common/smooth-weighted-round-robin.ts +48 -0
- package/bundle/src/utils/providers/openai/codex-worker.test.ts +70 -1
- package/bundle/src/utils/providers/openai/codex-worker.ts +18 -6
- package/bundle/src/utils/providers/openai/openai-dispatcher.test.ts +109 -6
- package/bundle/src/utils/providers/openai/openai-dispatcher.ts +72 -55
- package/bundle/web-dist/client/assets/{index-CN7eSKO4.js → index-D1ExP77_.js} +3 -3
- package/bundle/web-dist/client/assets/{logs-NubdF_C_.js → logs-vuI-Zgnq.js} +1 -1
- package/bundle/web-dist/client/assets/routes-0Fki0JEi.js +5 -0
- package/bundle/web-dist/client/assets/{sd.generated-BrUnRt0B.js → sd.generated-WA5SGJYZ.js} +1 -1
- package/bundle/web-dist/client/assets/{services.generated-AsEvHbJ7.js → services.generated-DzZLnb7S.js} +1 -1
- package/bundle/web-dist/client/assets/{show-BvDjIZCX.js → show-BreAgaFH.js} +1 -1
- package/bundle/web-dist/client/assets/tokens-4VgHQtXd.js +1 -0
- package/bundle/web-dist/client/assets/use-update-token-mutation-BV6msjuy.js +1 -0
- package/bundle/web-dist/client/index.html +2 -2
- package/bundle/web-dist/server/assets/{lazyRouteComponent-BswulcOx.js → lazyRouteComponent-BrPAFtBA.js} +1 -1
- package/bundle/web-dist/server/assets/{logs-Bk9C0ELo.js → logs-CKM6W3W6.js} +3 -3
- package/bundle/web-dist/server/assets/{routes-D9OiKoqa.js → routes-DhIpPgXz.js} +130 -7
- package/bundle/web-dist/server/assets/{sd.generated-DRuQSIKo.js → sd.generated-Cwg2f53z.js} +1 -0
- package/bundle/web-dist/server/assets/{services.generated-Dp0uKgpV.js → services.generated-CsotZP44.js} +5 -5
- package/bundle/web-dist/server/assets/{show-3GokIDEJ.js → show-CV2UFjaH.js} +3 -3
- package/bundle/web-dist/server/assets/{tokens-Cjw6cQxZ.js → tokens-DfGj1y2v.js} +5 -5
- package/bundle/web-dist/server/assets/use-update-token-mutation-BL96w-Pg.js +7 -0
- package/bundle/web-dist/server/entry-server.generated.js +7 -7
- package/package.json +1 -1
- package/skills/qgrid/SKILL.md +1 -1
- package/skills/qgrid/references/anthropic-claude-code-runtime.md +2 -2
- package/skills/qgrid/references/cli-env-and-server-boot.md +9 -9
- package/skills/qgrid/references/decision-rationale.md +23 -0
- package/skills/qgrid/references/openai-codex-runtime.md +4 -3
- package/skills/qgrid/references/prompt-cache-and-usage.md +2 -0
- package/skills/qgrid/references/provider-runtime-differences.md +2 -1
- package/skills/qgrid/references/repo-map.md +2 -0
- package/skills/qgrid/references/token-auth-quota-lifecycle.md +8 -3
- package/skills/qgrid/references/verification-and-debugging.md +3 -0
- package/bundle/web-dist/client/assets/routes-NKxXqHvs.js +0 -5
- package/bundle/web-dist/client/assets/tokens-XAhJyLqj.js +0 -1
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
type GenerateStreamCallbacks,
|
|
19
19
|
type ProviderDispatcher,
|
|
20
20
|
} from "../common/provider-dispatcher";
|
|
21
|
+
import { SmoothWeightedRoundRobin } from "../common/smooth-weighted-round-robin";
|
|
21
22
|
import { CodexAppServerWorker, type StreamCallbacks, type TurnRequest } from "./codex-worker";
|
|
22
23
|
import {
|
|
23
24
|
readOpenAIQuotaUsage,
|
|
@@ -92,6 +93,7 @@ type QueueItem = {
|
|
|
92
93
|
type TokenMetadata = {
|
|
93
94
|
name: string;
|
|
94
95
|
quotaThreshold?: number | null;
|
|
96
|
+
weight: number;
|
|
95
97
|
};
|
|
96
98
|
|
|
97
99
|
type TokenEligibility = {
|
|
@@ -104,8 +106,9 @@ type TokenEligibility = {
|
|
|
104
106
|
export class OpenAIDispatcher implements ProviderDispatcher {
|
|
105
107
|
workerPool = new Map<number, CodexAppServerWorker[]>();
|
|
106
108
|
tokenMetadata = new Map<number, TokenMetadata>();
|
|
107
|
-
rrCursor = 0;
|
|
108
109
|
queue: QueueItem[] = [];
|
|
110
|
+
private readonly weightedSelector = new SmoothWeightedRoundRobin();
|
|
111
|
+
private readonly workerCursorByToken = new Map<number, number>();
|
|
109
112
|
private draining = false;
|
|
110
113
|
private drainAgain = false;
|
|
111
114
|
|
|
@@ -123,6 +126,7 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
123
126
|
t.name,
|
|
124
127
|
t.credentials as OpenAICredentials,
|
|
125
128
|
t.quota_threshold,
|
|
129
|
+
t.weight,
|
|
126
130
|
);
|
|
127
131
|
if (!t.active) this.onTokenDeactivated(t.id);
|
|
128
132
|
}),
|
|
@@ -133,8 +137,11 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
133
137
|
this.rejectAllQueued("DISPATCHER_SHUTDOWN");
|
|
134
138
|
const kills = [...this.workerPool.values()].flat().map((w) => w.kill());
|
|
135
139
|
await Promise.allSettled(kills);
|
|
140
|
+
for (const id of this.tokenMetadata.keys()) this.weightedSelector.removeToken(id);
|
|
136
141
|
this.workerPool.clear();
|
|
137
142
|
this.tokenMetadata.clear();
|
|
143
|
+
this.workerCursorByToken.clear();
|
|
144
|
+
this.weightedSelector.resetScores();
|
|
138
145
|
this.rateLimitsCache.clear();
|
|
139
146
|
}
|
|
140
147
|
|
|
@@ -145,10 +152,11 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
145
152
|
name: string,
|
|
146
153
|
credentials: OpenAICredentials,
|
|
147
154
|
quotaThreshold?: number | null,
|
|
155
|
+
weight = 1,
|
|
148
156
|
): Promise<void> {
|
|
149
|
-
this.setTokenMetadata(id, name, quotaThreshold);
|
|
157
|
+
this.setTokenMetadata(id, name, quotaThreshold, weight);
|
|
150
158
|
if (this.workerPool.has(id)) return;
|
|
151
|
-
await this.spawnWorkers(id, name, credentials, quotaThreshold);
|
|
159
|
+
await this.spawnWorkers(id, name, credentials, quotaThreshold, weight);
|
|
152
160
|
}
|
|
153
161
|
|
|
154
162
|
async onTokenRemoved(id: number): Promise<void> {
|
|
@@ -156,6 +164,8 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
156
164
|
const tokenName = workers[0]?.tokenName;
|
|
157
165
|
this.workerPool.delete(id);
|
|
158
166
|
this.tokenMetadata.delete(id);
|
|
167
|
+
this.weightedSelector.removeToken(id);
|
|
168
|
+
this.workerCursorByToken.delete(id);
|
|
159
169
|
this.rateLimitsCache.delete(id);
|
|
160
170
|
await Promise.allSettled(workers.map((w) => w.kill()));
|
|
161
171
|
if (this.getAllReadyActiveWorkers().length === 0) {
|
|
@@ -172,14 +182,15 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
172
182
|
name: string,
|
|
173
183
|
credentials: OpenAICredentials,
|
|
174
184
|
quotaThreshold?: number | null,
|
|
185
|
+
weight = 1,
|
|
175
186
|
): Promise<void> {
|
|
176
187
|
const existing = this.workerPool.get(id) ?? [];
|
|
177
188
|
const renamed = existing.some((w) => w.tokenName !== name);
|
|
178
|
-
this.setTokenMetadata(id, name, quotaThreshold);
|
|
189
|
+
this.setTokenMetadata(id, name, quotaThreshold, weight);
|
|
179
190
|
if (renamed) this.rateLimitsCache.delete(id);
|
|
180
191
|
|
|
181
192
|
if (existing.length === 0) {
|
|
182
|
-
await this.spawnWorkers(id, name, credentials, quotaThreshold);
|
|
193
|
+
await this.spawnWorkers(id, name, credentials, quotaThreshold, weight);
|
|
183
194
|
return;
|
|
184
195
|
}
|
|
185
196
|
|
|
@@ -193,15 +204,17 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
193
204
|
this.workerPool.delete(id);
|
|
194
205
|
this.rateLimitsCache.delete(id);
|
|
195
206
|
await Promise.allSettled(existing.map((w) => w.kill()));
|
|
196
|
-
await this.spawnWorkers(id, name, credentials, quotaThreshold);
|
|
207
|
+
await this.spawnWorkers(id, name, credentials, quotaThreshold, weight);
|
|
197
208
|
}
|
|
198
209
|
|
|
199
210
|
onTokenDeactivated(id: number): void {
|
|
200
211
|
const workers = this.workerPool.get(id) ?? [];
|
|
201
212
|
const tokenName = workers[0]?.tokenName;
|
|
213
|
+
const changed = workers.some((worker) => worker.active);
|
|
202
214
|
workers.forEach((w) => {
|
|
203
215
|
w.active = false;
|
|
204
216
|
});
|
|
217
|
+
if (changed) this.weightedSelector.resetScores();
|
|
205
218
|
if (this.getAllReadyActiveWorkers().length === 0) {
|
|
206
219
|
this.rejectAllQueued("NO_ACTIVE_WORKERS");
|
|
207
220
|
}
|
|
@@ -212,9 +225,11 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
212
225
|
onTokenActivated(id: number): void {
|
|
213
226
|
const workers = this.workerPool.get(id) ?? [];
|
|
214
227
|
const tokenName = workers[0]?.tokenName;
|
|
228
|
+
const changed = workers.some((worker) => !worker.active);
|
|
215
229
|
workers.forEach((w) => {
|
|
216
230
|
w.active = true;
|
|
217
231
|
});
|
|
232
|
+
if (changed) this.weightedSelector.resetScores();
|
|
218
233
|
this.requestDrainQueue();
|
|
219
234
|
const label = tokenName ?? `token ${id}`;
|
|
220
235
|
logger.info(`workers activated: ${label}`);
|
|
@@ -226,6 +241,7 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
226
241
|
name: string;
|
|
227
242
|
credentials: OpenAICredentials;
|
|
228
243
|
quotaThreshold?: number | null;
|
|
244
|
+
weight: number;
|
|
229
245
|
}>,
|
|
230
246
|
): Promise<void> {
|
|
231
247
|
const next = new Set(rows.map((r) => r.id));
|
|
@@ -235,9 +251,15 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
235
251
|
|
|
236
252
|
for (const row of rows) {
|
|
237
253
|
if (this.workerPool.has(row.id)) {
|
|
238
|
-
await this.onTokenUpdated(
|
|
254
|
+
await this.onTokenUpdated(
|
|
255
|
+
row.id,
|
|
256
|
+
row.name,
|
|
257
|
+
row.credentials,
|
|
258
|
+
row.quotaThreshold,
|
|
259
|
+
row.weight,
|
|
260
|
+
);
|
|
239
261
|
} else {
|
|
240
|
-
await this.onTokenAdded(row.id, row.name, row.credentials, row.quotaThreshold);
|
|
262
|
+
await this.onTokenAdded(row.id, row.name, row.credentials, row.quotaThreshold, row.weight);
|
|
241
263
|
}
|
|
242
264
|
this.onTokenActivated(row.id);
|
|
243
265
|
}
|
|
@@ -273,7 +295,10 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
273
295
|
// 이미지 생성은 non-stream 전용(R2): codex 가 이미지 델타를 주지 않고 완성 base64 를
|
|
274
296
|
// 한 번에 준다. 스트리밍 경로로 이미지 요청이 오면 명시적 에러.
|
|
275
297
|
if (req.imageGeneration) {
|
|
276
|
-
throw new ImageGenerationError(
|
|
298
|
+
throw new ImageGenerationError(
|
|
299
|
+
"gate",
|
|
300
|
+
"image generation is not supported on the streaming path",
|
|
301
|
+
);
|
|
277
302
|
}
|
|
278
303
|
const excludedTokenIds = new Set<number>();
|
|
279
304
|
const reuseWorker = await this.acquireReuseWorker(req, excludedTokenIds);
|
|
@@ -341,8 +366,7 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
341
366
|
}
|
|
342
367
|
|
|
343
368
|
// ── Worker selection ────────────────────────────────────────────
|
|
344
|
-
//
|
|
345
|
-
// TODO: 추후 다른 알고리즘으로 변경 가능하게 지원
|
|
369
|
+
// Quota-eligible idle token을 weight로 고른 뒤, 그 token 안에서 worker를 순환한다.
|
|
346
370
|
async acquireIdleWorker(
|
|
347
371
|
excludedTokenIds = new Set<number>(),
|
|
348
372
|
): Promise<CodexAppServerWorker | null> {
|
|
@@ -350,18 +374,29 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
350
374
|
if (eligibility.readyActiveTokenIds.size === 0) return null;
|
|
351
375
|
if (eligibility.eligibleTokenIds.size === 0) this.throwQuotaThresholdExceeded(eligibility);
|
|
352
376
|
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
377
|
+
const workersByToken = new Map<number, CodexAppServerWorker[]>();
|
|
378
|
+
for (const worker of this.getAllReadyActiveWorkers()) {
|
|
379
|
+
if (!eligibility.eligibleTokenIds.has(worker.tokenId) || !worker.hasCapacity) continue;
|
|
380
|
+
const workers = workersByToken.get(worker.tokenId) ?? [];
|
|
381
|
+
workers.push(worker);
|
|
382
|
+
workersByToken.set(worker.tokenId, workers);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const selectedTokenId = this.weightedSelector.select(new Set(workersByToken.keys()));
|
|
386
|
+
if (selectedTokenId === null) return null;
|
|
387
|
+
const workers = workersByToken.get(selectedTokenId)!;
|
|
388
|
+
const cursor = this.workerCursorByToken.get(selectedTokenId) ?? 0;
|
|
389
|
+
for (let offset = 0; offset < workers.length; offset++) {
|
|
390
|
+
const index = (cursor + offset) % workers.length;
|
|
391
|
+
const worker = workers[index]!;
|
|
392
|
+
if (worker.tryAcquireTurn()) {
|
|
393
|
+
this.workerCursorByToken.set(selectedTokenId, (index + 1) % workers.length);
|
|
394
|
+
return worker;
|
|
362
395
|
}
|
|
363
396
|
}
|
|
364
|
-
|
|
397
|
+
throw new Error(
|
|
398
|
+
`weighted token ${selectedTokenId} lost idle capacity during synchronous acquire`,
|
|
399
|
+
);
|
|
365
400
|
}
|
|
366
401
|
|
|
367
402
|
getAllReadyActiveWorkers(): CodexAppServerWorker[] {
|
|
@@ -423,19 +458,17 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
423
458
|
this.queue = [];
|
|
424
459
|
}
|
|
425
460
|
|
|
426
|
-
async drainQueue(
|
|
461
|
+
async drainQueue(): Promise<void> {
|
|
427
462
|
if (this.draining) {
|
|
428
463
|
this.drainAgain = true;
|
|
429
464
|
return;
|
|
430
465
|
}
|
|
431
466
|
|
|
432
467
|
this.draining = true;
|
|
433
|
-
let worker = releasedWorker;
|
|
434
468
|
try {
|
|
435
469
|
do {
|
|
436
470
|
this.drainAgain = false;
|
|
437
|
-
await this.drainQueueOnce(
|
|
438
|
-
worker = undefined;
|
|
471
|
+
await this.drainQueueOnce();
|
|
439
472
|
} while (this.drainAgain);
|
|
440
473
|
} catch (e) {
|
|
441
474
|
logger.warn(`drainQueue failed: ${(e as Error).message}`);
|
|
@@ -444,20 +477,7 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
444
477
|
}
|
|
445
478
|
}
|
|
446
479
|
|
|
447
|
-
private async drainQueueOnce(
|
|
448
|
-
if (
|
|
449
|
-
releasedWorker &&
|
|
450
|
-
this.queue.length > 0 &&
|
|
451
|
-
releasedWorker.isReady &&
|
|
452
|
-
releasedWorker.active &&
|
|
453
|
-
(await this.isWorkerEligibleForQueueItem(releasedWorker, this.queue[0]!)) &&
|
|
454
|
-
releasedWorker.tryAcquireTurn()
|
|
455
|
-
) {
|
|
456
|
-
const next = this.queue.shift()!;
|
|
457
|
-
next.resolve(releasedWorker);
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
|
|
480
|
+
private async drainQueueOnce(): Promise<void> {
|
|
461
481
|
while (this.queue.length > 0) {
|
|
462
482
|
const next = this.queue[0]!;
|
|
463
483
|
let worker: CodexAppServerWorker | null;
|
|
@@ -487,7 +507,7 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
487
507
|
return await execute(worker);
|
|
488
508
|
} finally {
|
|
489
509
|
worker.releaseTurn();
|
|
490
|
-
this.requestDrainQueue(
|
|
510
|
+
this.requestDrainQueue();
|
|
491
511
|
}
|
|
492
512
|
}
|
|
493
513
|
|
|
@@ -586,8 +606,9 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
586
606
|
tokenName: string,
|
|
587
607
|
credentials: OpenAICredentials,
|
|
588
608
|
quotaThreshold?: number | null,
|
|
609
|
+
weight = 1,
|
|
589
610
|
): Promise<void> {
|
|
590
|
-
this.setTokenMetadata(tokenId, tokenName, quotaThreshold);
|
|
611
|
+
this.setTokenMetadata(tokenId, tokenName, quotaThreshold, weight);
|
|
591
612
|
const workers: CodexAppServerWorker[] = [];
|
|
592
613
|
for (let i = 0; i < WORKERS_PER_TOKEN; i++) {
|
|
593
614
|
if (i > 0) await sleep(SPAWN_INTERVAL_MS);
|
|
@@ -651,14 +672,18 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
651
672
|
return entry;
|
|
652
673
|
}
|
|
653
674
|
|
|
654
|
-
private requestDrainQueue(
|
|
655
|
-
void this.drainQueue(
|
|
656
|
-
logger.warn(`drainQueue failed: ${(e as Error).message}`),
|
|
657
|
-
);
|
|
675
|
+
private requestDrainQueue(): void {
|
|
676
|
+
void this.drainQueue().catch((e) => logger.warn(`drainQueue failed: ${(e as Error).message}`));
|
|
658
677
|
}
|
|
659
678
|
|
|
660
|
-
private setTokenMetadata(
|
|
661
|
-
|
|
679
|
+
private setTokenMetadata(
|
|
680
|
+
id: number,
|
|
681
|
+
name: string,
|
|
682
|
+
quotaThreshold: number | null | undefined,
|
|
683
|
+
weight: number,
|
|
684
|
+
): void {
|
|
685
|
+
this.tokenMetadata.set(id, { name, quotaThreshold, weight });
|
|
686
|
+
this.weightedSelector.setToken(id, weight);
|
|
662
687
|
}
|
|
663
688
|
|
|
664
689
|
private getQuotaThreshold(tokenId: number): number | null | undefined {
|
|
@@ -725,14 +750,6 @@ export class OpenAIDispatcher implements ProviderDispatcher {
|
|
|
725
750
|
return { readyActiveTokenIds, eligibleTokenIds, overThresholdTokens, thresholdedTokenCount };
|
|
726
751
|
}
|
|
727
752
|
|
|
728
|
-
private async isWorkerEligibleForQueueItem(
|
|
729
|
-
worker: CodexAppServerWorker,
|
|
730
|
-
item: QueueItem,
|
|
731
|
-
): Promise<boolean> {
|
|
732
|
-
if (item.excludedTokenIds.has(worker.tokenId)) return false;
|
|
733
|
-
return this.isTokenQuotaEligible(worker.tokenId, worker.tokenName);
|
|
734
|
-
}
|
|
735
|
-
|
|
736
753
|
private async isTokenQuotaEligible(
|
|
737
754
|
tokenId: number,
|
|
738
755
|
tokenName: string,
|