@cartanova/qgrid-cli 2.7.9 → 2.8.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/bundle/dist/application/qgrid/oauth.js +5 -2
- package/bundle/dist/application/qgrid/qgrid.types.js +1 -1
- package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +12 -7
- package/bundle/dist/utils/providers/anthropic/anthropic-quota.js +7 -5
- package/bundle/src/application/qgrid/oauth.test.ts +28 -0
- package/bundle/src/application/qgrid/oauth.ts +5 -0
- package/bundle/src/application/qgrid/qgrid.types.ts +1 -1
- package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.test.ts +36 -0
- package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.ts +20 -7
- package/bundle/src/utils/providers/anthropic/anthropic-quota.test.ts +22 -0
- package/bundle/src/utils/providers/anthropic/anthropic-quota.ts +16 -2
- package/bundle/web-dist/client/assets/{index-D59Q_9Z4.js → index-D208L33c.js} +3 -3
- package/bundle/web-dist/client/assets/{logs-BHVfI8ut.js → logs-noN5Aka6.js} +1 -1
- package/bundle/web-dist/client/assets/{monit-EkLxH1oN.js → monit-Ber13xcA.js} +1 -1
- package/bundle/web-dist/client/assets/{routes-Kbvuclip.js → routes-3_xetpSK.js} +1 -1
- package/bundle/web-dist/client/assets/{settings-B4uTFP3J.js → settings-C5sHCg8X.js} +1 -1
- package/bundle/web-dist/client/assets/{show-BjIdSPTd.js → show-BhTgGnkc.js} +1 -1
- package/bundle/web-dist/client/assets/{tokens-BbCSxbFu.js → tokens-R7obwdAV.js} +1 -1
- package/bundle/web-dist/client/assets/{use-oauth-login-flow-4OltfVAd.js → use-oauth-login-flow-CRAqRZXX.js} +1 -1
- package/bundle/web-dist/client/index.html +1 -1
- package/bundle/web-dist/server/entry-server.generated.js +1 -1
- package/package.json +1 -1
- package/skills/qgrid/SKILL.md +7 -2
- package/skills/qgrid/references/ai-sdk-provider-contract.md +5 -0
- package/skills/qgrid/references/anthropic-claude-code-runtime.md +5 -1
- package/skills/qgrid/references/decision-rationale.md +3 -1
- package/skills/qgrid/references/docs-workflow.md +3 -3
- package/skills/qgrid/references/sonamu-api-web-flow.md +4 -2
- package/skills/qgrid/references/token-auth-quota-lifecycle.md +5 -2
- package/skills/qgrid/references/verification-and-debugging.md +19 -16
|
@@ -26,7 +26,11 @@ import {
|
|
|
26
26
|
import { serializeAndValidateDispatchSchema } from "../common/schema-validation";
|
|
27
27
|
import { SmoothWeightedRoundRobin } from "../common/smooth-weighted-round-robin";
|
|
28
28
|
import { assertSupportedOneMillionSuffix, canonicalAnthropicModel } from "./anthropic-constants";
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
invalidateAnthropicQuotaUsage,
|
|
31
|
+
readAnthropicQuotaUsage,
|
|
32
|
+
type AnthropicQuotaUsageResult,
|
|
33
|
+
} from "./anthropic-quota";
|
|
30
34
|
import { makeAnthropicWorkerId, runClaudeSession } from "./claude-session";
|
|
31
35
|
|
|
32
36
|
const logger = getLogger(["qgrid", "anthropic-dispatcher"]);
|
|
@@ -163,7 +167,7 @@ export class AnthropicDispatcher implements ProviderDispatcher {
|
|
|
163
167
|
|
|
164
168
|
// 지정 요청은 해당 토큰만 quota 판정하고 weighted 상태를 건드리지 않는다.
|
|
165
169
|
// 미지정 요청만 quota 통과 후보로 smooth weighted round-robin 을 진행한다.
|
|
166
|
-
private async selectToken(preferredTokenId?: number): Promise<PooledToken | null> {
|
|
170
|
+
private async selectToken(model: string, preferredTokenId?: number): Promise<PooledToken | null> {
|
|
167
171
|
const preferred =
|
|
168
172
|
preferredTokenId === undefined ? undefined : this.tokenPool.get(preferredTokenId);
|
|
169
173
|
if (preferredTokenId !== undefined && !preferred) {
|
|
@@ -172,7 +176,7 @@ export class AnthropicDispatcher implements ProviderDispatcher {
|
|
|
172
176
|
|
|
173
177
|
const rows = preferred ? [preferred] : [...this.tokenPool.values()];
|
|
174
178
|
if (rows.length === 0) return null;
|
|
175
|
-
const { eligible, overThresholdTokens } = await this.filterEligibleTokens(rows);
|
|
179
|
+
const { eligible, overThresholdTokens } = await this.filterEligibleTokens(rows, model);
|
|
176
180
|
if (eligible.length === 0) {
|
|
177
181
|
logger.warn("quota_threshold gate: all_exceeded", {
|
|
178
182
|
provider: "anthropic",
|
|
@@ -193,7 +197,10 @@ export class AnthropicDispatcher implements ProviderDispatcher {
|
|
|
193
197
|
return this.tokenPool.get(selectedId) ?? null;
|
|
194
198
|
}
|
|
195
199
|
|
|
196
|
-
private async filterEligibleTokens(
|
|
200
|
+
private async filterEligibleTokens(
|
|
201
|
+
rows: PooledToken[],
|
|
202
|
+
model: string,
|
|
203
|
+
): Promise<{
|
|
197
204
|
eligible: PooledToken[];
|
|
198
205
|
overThresholdTokens: Array<{ tokenName: string; threshold: number }>;
|
|
199
206
|
}> {
|
|
@@ -207,7 +214,7 @@ export class AnthropicDispatcher implements ProviderDispatcher {
|
|
|
207
214
|
const checks = await Promise.allSettled(
|
|
208
215
|
thresholded.map(async (token) => ({
|
|
209
216
|
token,
|
|
210
|
-
result: await readAnthropicQuotaUsage(token.credentials.accessToken),
|
|
217
|
+
result: await readAnthropicQuotaUsage(token.credentials.accessToken, model),
|
|
211
218
|
})),
|
|
212
219
|
);
|
|
213
220
|
|
|
@@ -314,7 +321,7 @@ export class AnthropicDispatcher implements ProviderDispatcher {
|
|
|
314
321
|
const model = canonicalAnthropicModel(req.model);
|
|
315
322
|
const jsonSchema = serializeAndValidateDispatchSchema(req.outputSchema, "anthropic");
|
|
316
323
|
|
|
317
|
-
const token = await this.selectToken(req.preferredTokenId);
|
|
324
|
+
const token = await this.selectToken(model, req.preferredTokenId);
|
|
318
325
|
if (!token) throw new Error("No anthropic tokens available");
|
|
319
326
|
|
|
320
327
|
const exec = async (): Promise<GenerateResult> => {
|
|
@@ -359,7 +366,13 @@ export class AnthropicDispatcher implements ProviderDispatcher {
|
|
|
359
366
|
onDelta,
|
|
360
367
|
);
|
|
361
368
|
|
|
362
|
-
if (session.quotaExhausted)
|
|
369
|
+
if (session.quotaExhausted) {
|
|
370
|
+
invalidateAnthropicQuotaUsage(token.credentials.accessToken);
|
|
371
|
+
if (accessToken !== token.credentials.accessToken) {
|
|
372
|
+
invalidateAnthropicQuotaUsage(accessToken);
|
|
373
|
+
}
|
|
374
|
+
throw new Error(`quota exhausted (${token.name})`);
|
|
375
|
+
}
|
|
363
376
|
if (session.isError) {
|
|
364
377
|
// 진단(SON-495): isError 판정 사유(subtype/terminal_reason)를 메시지 앞에 드러낸다.
|
|
365
378
|
// 그동안 detail 이 session.text(완전한 JSON 본문)뿐이라 "schema 위반인지 / max retries 인지 /
|
|
@@ -34,6 +34,28 @@ describe("readAnthropicQuotaUsage", () => {
|
|
|
34
34
|
});
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
+
it("uses the separate Fable 5 limit only for Fable requests", async () => {
|
|
38
|
+
const usage = {
|
|
39
|
+
data: {
|
|
40
|
+
five_hour: { utilization: 10, resets_at: null },
|
|
41
|
+
seven_day_overage_included: { utilization: 100, resets_at: null },
|
|
42
|
+
},
|
|
43
|
+
cachedAt: Date.now(),
|
|
44
|
+
};
|
|
45
|
+
fetchUsageWithMetaMock.mockResolvedValue(usage);
|
|
46
|
+
|
|
47
|
+
await expect(readAnthropicQuotaUsage("access-token", "claude-fable-5")).resolves.toMatchObject({
|
|
48
|
+
kind: "ok",
|
|
49
|
+
utilizationPct: 100,
|
|
50
|
+
});
|
|
51
|
+
await expect(
|
|
52
|
+
readAnthropicQuotaUsage("access-token", "claude-sonnet-4-6"),
|
|
53
|
+
).resolves.toMatchObject({
|
|
54
|
+
kind: "ok",
|
|
55
|
+
utilizationPct: 10,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
37
59
|
it("keeps low percent utilization unchanged", async () => {
|
|
38
60
|
fetchUsageWithMetaMock.mockResolvedValueOnce({
|
|
39
61
|
data: { five_hour: { utilization: 1, resets_at: null } },
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
fetchUsageWithMeta,
|
|
3
|
+
invalidateAnthropicQuotaUsage,
|
|
4
|
+
} from "../../../application/qgrid/oauth";
|
|
5
|
+
|
|
6
|
+
export { invalidateAnthropicQuotaUsage };
|
|
2
7
|
|
|
3
8
|
export type AnthropicQuotaUsageResult =
|
|
4
9
|
| { kind: "ok"; utilizationPct: number; cacheAgeMs: number }
|
|
@@ -6,12 +11,21 @@ export type AnthropicQuotaUsageResult =
|
|
|
6
11
|
|
|
7
12
|
export async function readAnthropicQuotaUsage(
|
|
8
13
|
accessToken: string,
|
|
14
|
+
model?: string,
|
|
9
15
|
): Promise<AnthropicQuotaUsageResult> {
|
|
10
16
|
try {
|
|
11
17
|
const { data, cachedAt } = await fetchUsageWithMeta(accessToken);
|
|
12
18
|
if (data.error) return { kind: "lookup_failed", reason: data.error };
|
|
13
19
|
|
|
14
|
-
const
|
|
20
|
+
const utilizations = [data.five_hour?.utilization];
|
|
21
|
+
if (model === "claude-fable-5") {
|
|
22
|
+
utilizations.push(data.seven_day_overage_included?.utilization);
|
|
23
|
+
}
|
|
24
|
+
const utilization = Math.max(
|
|
25
|
+
...utilizations.filter(
|
|
26
|
+
(value): value is number => typeof value === "number" && Number.isFinite(value),
|
|
27
|
+
),
|
|
28
|
+
);
|
|
15
29
|
if (typeof utilization !== "number" || !Number.isFinite(utilization)) {
|
|
16
30
|
return { kind: "lookup_failed", reason: "five_hour utilization unavailable" };
|
|
17
31
|
}
|