@cartanova/qgrid-cli 2.9.2 → 2.9.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 +4 -3
- package/bundle/dist/application/qgrid/token-death.js +6 -22
- package/bundle/dist/application/qgrid/token-subscriber.js +2 -1
- package/bundle/dist/application/token/token.model.js +22 -17
- package/bundle/dist/utils/providers/common/model-cost.js +21 -13
- package/bundle/src/application/qgrid/qgrid.frame.test.ts +20 -0
- package/bundle/src/application/qgrid/qgrid.frame.ts +3 -2
- package/bundle/src/application/qgrid/token-death.test.ts +11 -10
- package/bundle/src/application/qgrid/token-death.ts +9 -21
- package/bundle/src/application/qgrid/token-subscriber.test.ts +5 -1
- package/bundle/src/application/qgrid/token-subscriber.ts +1 -0
- package/bundle/src/application/token/token.model.test.ts +24 -5
- package/bundle/src/application/token/token.model.ts +30 -23
- package/bundle/src/utils/providers/common/effort.test.ts +8 -0
- package/bundle/src/utils/providers/common/model-cost.test.ts +25 -12
- package/bundle/src/utils/providers/common/model-cost.ts +28 -14
- package/bundle/src/utils/providers/openai/openai-dispatcher.test.ts +1 -0
- package/bundle/web-dist/client/assets/{index-DH1n8kgG.js → index-fogJvajr.js} +3 -3
- package/bundle/web-dist/client/assets/{logs-D36O-NET.js → logs-CsY2cqvt.js} +1 -1
- package/bundle/web-dist/client/assets/{monit-DhZAaz9K.js → monit-ChgaLEcD.js} +1 -1
- package/bundle/web-dist/client/assets/{routes-C7q1CsSv.js → routes-BvyiTnOZ.js} +2 -2
- package/bundle/web-dist/client/assets/{settings-M_jfoIAe.js → settings-ZR2J9rWb.js} +1 -1
- package/bundle/web-dist/client/assets/{show-CORjMGQs.js → show-CjwO4-So.js} +1 -1
- package/bundle/web-dist/client/assets/{tokens-C7-j4VXD.js → tokens-Bx-hG1gm.js} +1 -1
- package/bundle/web-dist/client/assets/{use-oauth-login-flow-Fi62xbBc.js → use-oauth-login-flow-Bqax_icS.js} +1 -1
- package/bundle/web-dist/client/index.html +1 -1
- package/bundle/web-dist/server/assets/{routes-CxMHXfdH.js → routes-B408iqh1.js} +29 -14
- package/bundle/web-dist/server/entry-server.generated.js +3 -2
- package/package.json +1 -1
- package/skills/qgrid/SKILL.md +1 -0
- package/skills/qgrid/references/ai-sdk-provider-contract.md +1 -1
- package/skills/qgrid/references/openai-codex-runtime.md +71 -0
- package/skills/qgrid/references/provider-runtime-differences.md +1 -1
- package/skills/qgrid/references/sonamu-api-web-flow.md +1 -1
- package/skills/qgrid/references/token-auth-quota-lifecycle.md +3 -2
|
@@ -267,14 +267,13 @@ class TokenModelClass extends BaseModelClass<
|
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
/**
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
* 필요 상태는 기록한다. `marked`가 공유 DB 인스턴스들의 알림 발송 게이트다.
|
|
270
|
+
* Record confirmed expiry and remove the token from routing atomically.
|
|
271
|
+
* The last active token is also deactivated; its notification is urgent.
|
|
273
272
|
*/
|
|
274
273
|
async markReauthRequired(
|
|
275
274
|
id: number,
|
|
276
275
|
expectedCredentials: TokenCredentials,
|
|
277
|
-
): Promise<{ marked: boolean;
|
|
276
|
+
): Promise<{ marked: boolean; wasLastActive: boolean; staleCredentials: boolean }> {
|
|
278
277
|
const wdb = this.getPuri("w");
|
|
279
278
|
return wdb.transaction(async (trx) => {
|
|
280
279
|
const token = await trx
|
|
@@ -288,42 +287,50 @@ class TokenModelClass extends BaseModelClass<
|
|
|
288
287
|
.forUpdate()
|
|
289
288
|
.first();
|
|
290
289
|
if (!token || token.reauth_required) {
|
|
291
|
-
return { marked: false,
|
|
290
|
+
return { marked: false, wasLastActive: false, staleCredentials: false };
|
|
292
291
|
}
|
|
293
292
|
if (!isDeepStrictEqual(token.credentials, expectedCredentials)) {
|
|
294
|
-
return { marked: false,
|
|
293
|
+
return { marked: false, wasLastActive: false, staleCredentials: true };
|
|
295
294
|
}
|
|
296
295
|
|
|
297
296
|
await trx.knex.raw("SELECT pg_advisory_xact_lock(?, hashtext(?))", [
|
|
298
297
|
TOKEN_AUTH_DEATH_LOCK_CLASS_ID,
|
|
299
298
|
token.provider,
|
|
300
299
|
]);
|
|
301
|
-
const updated = await trx.knex.raw<{ rows: {
|
|
302
|
-
`
|
|
300
|
+
const updated = await trx.knex.raw<{ rows: { was_last_active: boolean }[] }>(
|
|
301
|
+
`WITH previous AS (
|
|
302
|
+
SELECT active AND NOT EXISTS (
|
|
303
|
+
SELECT 1 FROM tokens peer
|
|
304
|
+
WHERE peer.provider = tokens.provider AND peer.active
|
|
305
|
+
AND NOT peer.reauth_required AND peer.id <> tokens.id
|
|
306
|
+
) AS was_last_active FROM tokens WHERE id = ?
|
|
307
|
+
)
|
|
308
|
+
UPDATE tokens
|
|
303
309
|
SET reauth_required = true,
|
|
304
|
-
active =
|
|
305
|
-
WHEN active AND (SELECT count(*) FROM tokens peer
|
|
306
|
-
WHERE peer.provider = tokens.provider AND peer.active) > 1
|
|
307
|
-
THEN false
|
|
308
|
-
ELSE active
|
|
309
|
-
END
|
|
310
|
+
active = false
|
|
310
311
|
WHERE id = ? AND reauth_required = false
|
|
311
|
-
RETURNING
|
|
312
|
-
[id],
|
|
312
|
+
RETURNING (SELECT was_last_active FROM previous) AS was_last_active`,
|
|
313
|
+
[id, id],
|
|
313
314
|
);
|
|
314
315
|
const marked = updated.rows[0];
|
|
315
|
-
if (!marked) return { marked: false,
|
|
316
|
+
if (!marked) return { marked: false, wasLastActive: false, staleCredentials: false };
|
|
316
317
|
|
|
317
|
-
if (marked.
|
|
318
|
-
logger.error(
|
|
319
|
-
`refusing to auto-deactivate token ${id}: last active provider token — ` +
|
|
320
|
-
`systemic refresh failure suspected, keeping the pool non-empty`,
|
|
321
|
-
);
|
|
318
|
+
if (marked.was_last_active) {
|
|
319
|
+
logger.error(`deactivated expired token ${id}: no usable provider tokens remain`);
|
|
322
320
|
}
|
|
323
|
-
return { marked: true,
|
|
321
|
+
return { marked: true, wasLastActive: marked.was_last_active, staleCredentials: false };
|
|
324
322
|
});
|
|
325
323
|
}
|
|
326
324
|
|
|
325
|
+
/** Repair rows retained by the former last-active exception before pool reconciliation. */
|
|
326
|
+
async deactivateExpiredTokens(): Promise<void> {
|
|
327
|
+
await this.getPuri("w")
|
|
328
|
+
.table("tokens")
|
|
329
|
+
.where("reauth_required", true)
|
|
330
|
+
.where("active", true)
|
|
331
|
+
.update({ active: false });
|
|
332
|
+
}
|
|
333
|
+
|
|
327
334
|
@api({ httpMethod: "POST", clients: ["axios", "tanstack-mutation"] })
|
|
328
335
|
async reorder(ids: number[]): Promise<{ done: boolean }> {
|
|
329
336
|
const wdb = this.getPuri("w");
|
|
@@ -8,6 +8,14 @@ import {
|
|
|
8
8
|
} from "./effort";
|
|
9
9
|
|
|
10
10
|
describe("resolveOpenAIEffort", () => {
|
|
11
|
+
it("preserves every Astra Codex catalog effort including ultra", () => {
|
|
12
|
+
for (const effort of OPENAI_EFFORTS) {
|
|
13
|
+
expect(resolveOpenAIEffort("gpt-6-astra", effort)).toBe(effort);
|
|
14
|
+
}
|
|
15
|
+
expect(resolveOpenAIEffort("gpt-6-astra", "none")).toBeUndefined();
|
|
16
|
+
expect(resolveOpenAIEffort("gpt-6-astra", "minimal")).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
11
19
|
it("Codex 카탈로그 어휘는 그대로 통과한다", () => {
|
|
12
20
|
for (const effort of ["low", "medium", "high", "xhigh"]) {
|
|
13
21
|
expect(resolveOpenAIEffort("gpt-5.5", effort)).toBe(effort);
|
|
@@ -4,9 +4,18 @@ import { calculateCostUsd, getModelCosts } from "./model-cost";
|
|
|
4
4
|
|
|
5
5
|
describe("calculateCostUsd", () => {
|
|
6
6
|
it.each([
|
|
7
|
-
[
|
|
8
|
-
[
|
|
9
|
-
|
|
7
|
+
[272_000, 2.77],
|
|
8
|
+
[272_001, 5.51502],
|
|
9
|
+
])("Astra applies the long-context rate only above 272K (%i input)", (inputTokens, expected) => {
|
|
10
|
+
expect(
|
|
11
|
+
calculateCostUsd("openai/gpt-6-astra", { inputTokens, outputTokens: 1_000 }),
|
|
12
|
+
).toBeCloseTo(expected, 10);
|
|
13
|
+
});
|
|
14
|
+
it.each([
|
|
15
|
+
["gpt-6-astra", 10, 50, 1, 12.5],
|
|
16
|
+
["gpt-5.6-sol", 4, 20, 0.4, 5],
|
|
17
|
+
["gpt-5.6-terra", 2, 12, 0.2, 2.5],
|
|
18
|
+
["gpt-5.6-luna", 0.2, 1.2, 0.02, 0.25],
|
|
10
19
|
["gpt-5.5", 5, 30, 0.5, undefined],
|
|
11
20
|
["gpt-5.4", 2.5, 15, 0.25, undefined],
|
|
12
21
|
["gpt-5.4-mini", 0.75, 4.5, 0.075, undefined],
|
|
@@ -77,13 +86,15 @@ describe("calculateCostUsd", () => {
|
|
|
77
86
|
expect(getModelCosts("anthropic/claude-sonnet-4-6[1m]")).toBe(
|
|
78
87
|
getModelCosts("claude-sonnet-4-6"),
|
|
79
88
|
);
|
|
89
|
+
expect(getModelCosts("openai/gpt-6-astra")).toBe(getModelCosts("gpt-6-astra"));
|
|
80
90
|
expect(getModelCosts("openai/gpt-5.6-sol")).toBe(getModelCosts("gpt-5.6-sol"));
|
|
81
91
|
});
|
|
82
92
|
|
|
83
93
|
it.each([
|
|
84
|
-
["gpt-
|
|
85
|
-
["gpt-5.6-
|
|
86
|
-
["gpt-5.6-
|
|
94
|
+
["gpt-6-astra", 10, 50, 1, 12.5, 5.6125],
|
|
95
|
+
["gpt-5.6-sol", 4, 20, 0.4, 5, 2.245],
|
|
96
|
+
["gpt-5.6-terra", 2, 12, 0.2, 2.5, 1.3225],
|
|
97
|
+
["gpt-5.6-luna", 0.2, 1.2, 0.02, 0.25, 0.13225],
|
|
87
98
|
])(
|
|
88
99
|
"%s applies published input, output, cache-read, and cache-write pricing",
|
|
89
100
|
(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens, expectedCost) => {
|
|
@@ -106,9 +117,10 @@ describe("calculateCostUsd", () => {
|
|
|
106
117
|
);
|
|
107
118
|
|
|
108
119
|
it.each([
|
|
109
|
-
["gpt-
|
|
110
|
-
["gpt-5.6-
|
|
111
|
-
["gpt-5.6-
|
|
120
|
+
["gpt-6-astra", 2.475],
|
|
121
|
+
["gpt-5.6-sol", 0.99],
|
|
122
|
+
["gpt-5.6-terra", 0.498],
|
|
123
|
+
["gpt-5.6-luna", 0.0498],
|
|
112
124
|
])("%s applies the published long-context surcharge", (model, expectedCost) => {
|
|
113
125
|
expect(
|
|
114
126
|
calculateCostUsd(model, {
|
|
@@ -120,9 +132,10 @@ describe("calculateCostUsd", () => {
|
|
|
120
132
|
});
|
|
121
133
|
|
|
122
134
|
it.each([
|
|
123
|
-
["gpt-
|
|
124
|
-
["gpt-5.6-
|
|
125
|
-
["gpt-5.6-
|
|
135
|
+
["gpt-6-astra", 2.725],
|
|
136
|
+
["gpt-5.6-sol", 1.09],
|
|
137
|
+
["gpt-5.6-terra", 0.548],
|
|
138
|
+
["gpt-5.6-luna", 0.0548],
|
|
126
139
|
])("%s applies the long-context input multiplier to cache writes", (model, expectedCost) => {
|
|
127
140
|
expect(
|
|
128
141
|
calculateCostUsd(model, {
|
|
@@ -34,7 +34,7 @@ export interface ModelCosts {
|
|
|
34
34
|
|
|
35
35
|
// ── OpenAI ─────────────────────────────────────────────────────────
|
|
36
36
|
//
|
|
37
|
-
//
|
|
37
|
+
// Standard pricing verified 2026-09-07: https://developers.openai.com/api/docs/pricing
|
|
38
38
|
// 신모델 출시마다 단가가 바뀌므로(5.2→5.4→5.5) 모델 추가 시 반드시 공식 페이지 재확인해야함
|
|
39
39
|
|
|
40
40
|
// GPT-5.4에서 처음 도입된 long-context 할증 (5.2/5.3-codex는 해당 없음, 5.4-mini/nano는 공식 표에서 long-context 단가 없음)
|
|
@@ -48,38 +48,52 @@ const LONG_CONTEXT_272K: NonNullable<ModelCosts["longContext"]> = {
|
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
// OpenAI 모델 카탈로그. 단가와 함께 Codex 백엔드가 모델별로 받는 최대 reasoning effort 를 한 곳에 둔다.
|
|
51
|
-
// maxEffort 출처: ~/.codex/models_cache.json 의 supported_reasoning_levels (2026-09-
|
|
51
|
+
// maxEffort 출처: ~/.codex/models_cache.json 의 supported_reasoning_levels (2026-09-07 확인).
|
|
52
52
|
// 미기재 모델은 xhigh 까지만 받는다.
|
|
53
53
|
type OpenAIModelSpec = ModelCosts & { maxEffort?: OpenAIEffort };
|
|
54
54
|
|
|
55
55
|
const OPENAI_COSTS: Record<string, OpenAIModelSpec> = {
|
|
56
|
+
// GPT-6 Astra: Standard API-equivalent pricing, verified 2026-09-07.
|
|
57
|
+
// Codex subscription catalog supports ultra; the public API lists only through max.
|
|
58
|
+
// @see https://developers.openai.com/api/docs/models/gpt-6-astra
|
|
59
|
+
// @see https://developers.openai.com/api/docs/pricing
|
|
60
|
+
"gpt-6-astra": {
|
|
61
|
+
maxEffort: "ultra",
|
|
62
|
+
inputTokens: 10,
|
|
63
|
+
outputTokens: 50,
|
|
64
|
+
cachedInputTokens: 1,
|
|
65
|
+
cacheCreationInputTokens: 12.5,
|
|
66
|
+
longContext: LONG_CONTEXT_272K,
|
|
67
|
+
},
|
|
56
68
|
// GPT-5.6 Sol, Terra, Luna. cache write 단가는 외부 logger/manual usage 입력을 위해
|
|
57
69
|
// 유지한다. 현재 OpenAI 응답 usage 에 cache write 토큰 필드가 없으면 비용은 0으로 계산된다.
|
|
58
70
|
// @see https://developers.openai.com/api/docs/models/gpt-5.6-sol
|
|
59
71
|
// @see https://developers.openai.com/api/docs/models/gpt-5.6-terra
|
|
60
72
|
// @see https://developers.openai.com/api/docs/models/gpt-5.6-luna
|
|
73
|
+
// Sol promotional rates are available at least through 2026-11-21.
|
|
74
|
+
// No confirmed end date/replacement rate: recheck official pricing instead of auto-reverting.
|
|
61
75
|
"gpt-5.6-sol": {
|
|
62
76
|
maxEffort: "ultra",
|
|
63
|
-
inputTokens:
|
|
64
|
-
outputTokens:
|
|
65
|
-
cachedInputTokens: 0.
|
|
66
|
-
cacheCreationInputTokens:
|
|
77
|
+
inputTokens: 4,
|
|
78
|
+
outputTokens: 20,
|
|
79
|
+
cachedInputTokens: 0.4,
|
|
80
|
+
cacheCreationInputTokens: 5,
|
|
67
81
|
longContext: LONG_CONTEXT_272K,
|
|
68
82
|
},
|
|
69
83
|
"gpt-5.6-terra": {
|
|
70
84
|
maxEffort: "ultra",
|
|
71
|
-
inputTokens: 2
|
|
72
|
-
outputTokens:
|
|
73
|
-
cachedInputTokens: 0.
|
|
74
|
-
cacheCreationInputTokens:
|
|
85
|
+
inputTokens: 2,
|
|
86
|
+
outputTokens: 12,
|
|
87
|
+
cachedInputTokens: 0.2,
|
|
88
|
+
cacheCreationInputTokens: 2.5,
|
|
75
89
|
longContext: LONG_CONTEXT_272K,
|
|
76
90
|
},
|
|
77
91
|
"gpt-5.6-luna": {
|
|
78
92
|
maxEffort: "max",
|
|
79
|
-
inputTokens:
|
|
80
|
-
outputTokens:
|
|
81
|
-
cachedInputTokens: 0.
|
|
82
|
-
cacheCreationInputTokens:
|
|
93
|
+
inputTokens: 0.2,
|
|
94
|
+
outputTokens: 1.2,
|
|
95
|
+
cachedInputTokens: 0.02,
|
|
96
|
+
cacheCreationInputTokens: 0.25,
|
|
83
97
|
longContext: LONG_CONTEXT_272K,
|
|
84
98
|
},
|
|
85
99
|
// https://openai.com/index/introducing-gpt-5-5/ (2026-04 출시)
|
|
@@ -430,6 +430,7 @@ describe("effort 어휘 해석 (Codex 카탈로그 기준)", () => {
|
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
it("모델이 지원하는 값은 그대로 보내고, 상한 초과·공개 API 어휘는 reasoning 에서 빼 백엔드 기본값을 쓴다", async () => {
|
|
433
|
+
await expect(mappedEffortFor("gpt-6-astra", "ultra")).resolves.toBe("ultra");
|
|
433
434
|
await expect(mappedEffortFor("gpt-5.6-terra", "ultra")).resolves.toBe("ultra");
|
|
434
435
|
await expect(mappedEffortFor("gpt-5.5", "xhigh")).resolves.toBe("xhigh");
|
|
435
436
|
await expect(mappedEffortFor("gpt-5.5", "max")).resolves.toBeUndefined();
|