@cartanova/qgrid-cli 2.4.1 → 2.4.3
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/sonamu.config.js +2 -1
- package/bundle/dist/utils/providers/common/model-cost.js +22 -1
- package/bundle/dist/utils/providers/openai/codex-worker.js +19 -2
- package/bundle/src/sonamu.config.ts +3 -0
- 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/openai/codex-worker.test.ts +70 -1
- package/bundle/src/utils/providers/openai/codex-worker.ts +18 -6
- package/bundle/web-dist/client/assets/{index-CHRDevod.js → index-DsGDBrrH.js} +3 -3
- package/bundle/web-dist/client/assets/{logs-CFw7oJgw.js → logs-C6EMtwDn.js} +1 -1
- package/bundle/web-dist/client/assets/{routes-vhw9Wsg6.js → routes-De9iuL80.js} +1 -1
- package/bundle/web-dist/client/assets/{show-CKIx8CD_.js → show-BSW3PE7Z.js} +1 -1
- package/bundle/web-dist/client/assets/{tokens-Di4RowSm.js → tokens-BMsL6vk9.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/references/tool-calling-and-multiturn.md +7 -5
|
@@ -43,6 +43,37 @@ const LONG_CONTEXT_272K: NonNullable<ModelCosts["longContext"]> = {
|
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
const OPENAI_COSTS: Record<string, ModelCosts> = {
|
|
46
|
+
// GPT-5.6 Sol, Terra, Luna. OpenAI native API 는 1.05M context / 128K max output 이지만,
|
|
47
|
+
// qgrid 의 codex app-server 경로는 context_window=372K (max_context_window 도 372K,
|
|
48
|
+
// effective 95% ≈ 353K) 로 제한된다 — codex 0.144.1 models_cache 실측 (2026-07-10).
|
|
49
|
+
// cache write 는 uncached input 의 1.25x 로 과금되지만, codex app-server usage
|
|
50
|
+
// (TokenUsageBreakdown) 에 cache write 토큰 필드가 없어 현재 OpenAI 실행 경로의
|
|
51
|
+
// cost 계산에서는 항상 0 으로 잡힌다 → 그만큼 과소집계. 단가는 외부 logger/manual
|
|
52
|
+
// usage 입력과 향후 프로토콜 확장을 위해 유지한다.
|
|
53
|
+
// @see https://developers.openai.com/api/docs/models/gpt-5.6-sol
|
|
54
|
+
// @see https://developers.openai.com/api/docs/models/gpt-5.6-terra
|
|
55
|
+
// @see https://developers.openai.com/api/docs/models/gpt-5.6-luna
|
|
56
|
+
"gpt-5.6-sol": {
|
|
57
|
+
inputTokens: 5,
|
|
58
|
+
outputTokens: 30,
|
|
59
|
+
cachedInputTokens: 0.5,
|
|
60
|
+
cacheCreationInputTokens: 6.25,
|
|
61
|
+
longContext: LONG_CONTEXT_272K,
|
|
62
|
+
},
|
|
63
|
+
"gpt-5.6-terra": {
|
|
64
|
+
inputTokens: 2.5,
|
|
65
|
+
outputTokens: 15,
|
|
66
|
+
cachedInputTokens: 0.25,
|
|
67
|
+
cacheCreationInputTokens: 3.125,
|
|
68
|
+
longContext: LONG_CONTEXT_272K,
|
|
69
|
+
},
|
|
70
|
+
"gpt-5.6-luna": {
|
|
71
|
+
inputTokens: 1,
|
|
72
|
+
outputTokens: 6,
|
|
73
|
+
cachedInputTokens: 0.1,
|
|
74
|
+
cacheCreationInputTokens: 1.25,
|
|
75
|
+
longContext: LONG_CONTEXT_272K,
|
|
76
|
+
},
|
|
46
77
|
// https://openai.com/index/introducing-gpt-5-5/ (2026-04 출시)
|
|
47
78
|
"gpt-5.5": {
|
|
48
79
|
inputTokens: 5,
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
1
5
|
import { describe, expect, it, vi } from "vitest";
|
|
2
6
|
|
|
3
|
-
import { CodexAppServerWorker, type StreamCallbacks } from "./codex-worker";
|
|
7
|
+
import { CODEX_CONFIG_TOML, CodexAppServerWorker, type StreamCallbacks } from "./codex-worker";
|
|
4
8
|
|
|
5
9
|
function createWorkerWithFakeRpc() {
|
|
6
10
|
const worker = new CodexAppServerWorker({
|
|
@@ -349,6 +353,40 @@ describe("CodexAppServerWorker image generation capture", () => {
|
|
|
349
353
|
expect(result.imageAttempted).toBe(true);
|
|
350
354
|
});
|
|
351
355
|
|
|
356
|
+
it("removes generated image artifacts after a successful image turn", async () => {
|
|
357
|
+
const { worker } = createWorkerWithNotificationRpc(async (method, _params, handlers) => {
|
|
358
|
+
if (method === "thread/start") return { thread: { id: "t1" }, model: "gpt-test" };
|
|
359
|
+
if (method === "turn/start") {
|
|
360
|
+
handlers.get("item/completed")?.({
|
|
361
|
+
threadId: "t1",
|
|
362
|
+
item: imageItem("img-a", IMAGE_B64, "a red circle"),
|
|
363
|
+
} as never);
|
|
364
|
+
handlers.get("turn/completed")?.({
|
|
365
|
+
threadId: "t1",
|
|
366
|
+
turn: { status: "completed", durationMs: 100 },
|
|
367
|
+
} as never);
|
|
368
|
+
return { turn: { id: "turn-1" } };
|
|
369
|
+
}
|
|
370
|
+
throw new Error(`unexpected request: ${method}`);
|
|
371
|
+
});
|
|
372
|
+
const codexHome = mkdtempSync(join(tmpdir(), "qgrid-codex-worker-test-"));
|
|
373
|
+
const generatedImages = join(codexHome, "generated_images", "session");
|
|
374
|
+
mkdirSync(generatedImages, { recursive: true });
|
|
375
|
+
writeFileSync(join(generatedImages, "img-a.png"), "image");
|
|
376
|
+
(worker as unknown as { codexHome: string }).codexHome = codexHome;
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
await worker.executeTurn({
|
|
380
|
+
input: [{ type: "text", text: "two images", text_elements: [] }],
|
|
381
|
+
imageGeneration: true,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
expect(existsSync(join(codexHome, "generated_images"))).toBe(false);
|
|
385
|
+
} finally {
|
|
386
|
+
rmSync(codexHome, { recursive: true, force: true });
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
352
390
|
it("treats status='generating' completed item with valid base64 as a finished image", async () => {
|
|
353
391
|
const { worker } = createWorkerWithNotificationRpc(async (method, _params, handlers) => {
|
|
354
392
|
if (method === "thread/start") return { thread: { id: "t1" }, model: "gpt-test" };
|
|
@@ -488,6 +526,37 @@ describe("CodexAppServerWorker image generation capture", () => {
|
|
|
488
526
|
});
|
|
489
527
|
});
|
|
490
528
|
|
|
529
|
+
describe("CodexAppServerWorker CODEX_HOME artifacts", () => {
|
|
530
|
+
it("disables bundled system skills in the generated Codex config", () => {
|
|
531
|
+
expect(CODEX_CONFIG_TOML).toContain("[skills.bundled]");
|
|
532
|
+
expect(CODEX_CONFIG_TOML).toContain("enabled = false");
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it("removes the generated_images directory without touching other worker files", () => {
|
|
536
|
+
const worker = new CodexAppServerWorker({
|
|
537
|
+
tokenId: 1,
|
|
538
|
+
tokenName: "token",
|
|
539
|
+
accessToken: "access",
|
|
540
|
+
accountId: "account",
|
|
541
|
+
});
|
|
542
|
+
const codexHome = mkdtempSync(join(tmpdir(), "qgrid-codex-worker-test-"));
|
|
543
|
+
const generatedImages = join(codexHome, "generated_images", "session");
|
|
544
|
+
mkdirSync(generatedImages, { recursive: true });
|
|
545
|
+
writeFileSync(join(generatedImages, "img-a.png"), "image");
|
|
546
|
+
writeFileSync(join(codexHome, "state_5.sqlite"), "state");
|
|
547
|
+
(worker as unknown as { codexHome: string }).codexHome = codexHome;
|
|
548
|
+
|
|
549
|
+
try {
|
|
550
|
+
(worker as unknown as { cleanupGeneratedImages(): void }).cleanupGeneratedImages();
|
|
551
|
+
|
|
552
|
+
expect(existsSync(join(codexHome, "generated_images"))).toBe(false);
|
|
553
|
+
expect(existsSync(join(codexHome, "state_5.sqlite"))).toBe(true);
|
|
554
|
+
} finally {
|
|
555
|
+
rmSync(codexHome, { recursive: true, force: true });
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
|
|
491
560
|
describe("CodexAppServerWorker image generation thread", () => {
|
|
492
561
|
type WorkerInternals = {
|
|
493
562
|
createThread(req: unknown): Promise<{ threadId: string; model: string }>;
|
|
@@ -3,6 +3,11 @@ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
5
|
|
|
6
|
+
import {
|
|
7
|
+
CODEX_IMAGE_GENERATION_MODEL,
|
|
8
|
+
resolveImageGenerationOptions,
|
|
9
|
+
} from "../../../application/qgrid/qgrid-image-generation";
|
|
10
|
+
import { type ImageGenerationOptions } from "../../../application/qgrid/qgrid.types";
|
|
6
11
|
import { type Model } from "../../../codex-protocol/v2/Model";
|
|
7
12
|
import { type ModelListResponse } from "../../../codex-protocol/v2/ModelListResponse";
|
|
8
13
|
import { type ModelProviderCapabilitiesReadResponse } from "../../../codex-protocol/v2/ModelProviderCapabilitiesReadResponse";
|
|
@@ -12,11 +17,6 @@ import { type ThreadStartResponse } from "../../../codex-protocol/v2/ThreadStart
|
|
|
12
17
|
import { type TokenUsageBreakdown } from "../../../codex-protocol/v2/TokenUsageBreakdown";
|
|
13
18
|
import { type TurnStartParams } from "../../../codex-protocol/v2/TurnStartParams";
|
|
14
19
|
import { type TurnStartResponse } from "../../../codex-protocol/v2/TurnStartResponse";
|
|
15
|
-
import {
|
|
16
|
-
CODEX_IMAGE_GENERATION_MODEL,
|
|
17
|
-
resolveImageGenerationOptions,
|
|
18
|
-
} from "../../../application/qgrid/qgrid-image-generation";
|
|
19
|
-
import { type ImageGenerationOptions } from "../../../application/qgrid/qgrid.types";
|
|
20
20
|
import { type StreamCallbacks as CommonStreamCallbacks } from "../common/provider-dispatcher";
|
|
21
21
|
import { createTtftTracker } from "../common/ttft";
|
|
22
22
|
import { CodexRpcClient } from "./codex-rpc";
|
|
@@ -115,7 +115,7 @@ function isCompletedImageResult(result: unknown): result is string {
|
|
|
115
115
|
// instruction 블록(permissions/environment_context/skills, ~10KB)을 비활성화
|
|
116
116
|
// $CODEX_HOME/config.toml 로 써넣으면 codex 부팅 시 scan
|
|
117
117
|
// 통제불가: update_plan/request_user_input
|
|
118
|
-
const CODEX_CONFIG_TOML = `web_search = "disabled"
|
|
118
|
+
export const CODEX_CONFIG_TOML = `web_search = "disabled"
|
|
119
119
|
include_permissions_instructions = false
|
|
120
120
|
include_apps_instructions = false
|
|
121
121
|
include_environment_context = false
|
|
@@ -134,6 +134,9 @@ view_image = false
|
|
|
134
134
|
|
|
135
135
|
[skills]
|
|
136
136
|
include_instructions = false
|
|
137
|
+
|
|
138
|
+
[skills.bundled]
|
|
139
|
+
enabled = false
|
|
137
140
|
`;
|
|
138
141
|
|
|
139
142
|
// ── Worker ──────────────────────────────────────────────────────────
|
|
@@ -179,6 +182,7 @@ export class CodexAppServerWorker {
|
|
|
179
182
|
this.cachedCapabilities = undefined;
|
|
180
183
|
this.cachedModels = undefined;
|
|
181
184
|
const cwd = `${this.codexHome}/cwd`;
|
|
185
|
+
rmSync(this.codexHome, { recursive: true, force: true });
|
|
182
186
|
mkdirSync(cwd, { recursive: true });
|
|
183
187
|
// codex 내장 tool/web_search/instruction 블록 비활성화
|
|
184
188
|
writeFileSync(`${this.codexHome}/config.toml`, CODEX_CONFIG_TOML);
|
|
@@ -583,6 +587,12 @@ export class CodexAppServerWorker {
|
|
|
583
587
|
abort?.(error);
|
|
584
588
|
}
|
|
585
589
|
|
|
590
|
+
private cleanupGeneratedImages(): void {
|
|
591
|
+
try {
|
|
592
|
+
rmSync(`${this.codexHome}/generated_images`, { recursive: true, force: true });
|
|
593
|
+
} catch {}
|
|
594
|
+
}
|
|
595
|
+
|
|
586
596
|
private consumeTurnNotifications(
|
|
587
597
|
threadId: string,
|
|
588
598
|
model: string,
|
|
@@ -633,6 +643,7 @@ export class CodexAppServerWorker {
|
|
|
633
643
|
if (settled) return;
|
|
634
644
|
settled = true;
|
|
635
645
|
cleanup();
|
|
646
|
+
if (result.imageAttempted) this.cleanupGeneratedImages();
|
|
636
647
|
resolve(result);
|
|
637
648
|
};
|
|
638
649
|
|
|
@@ -751,6 +762,7 @@ export class CodexAppServerWorker {
|
|
|
751
762
|
if (settled) return;
|
|
752
763
|
settled = true;
|
|
753
764
|
cleanup();
|
|
765
|
+
if (result.imageAttempted) this.cleanupGeneratedImages();
|
|
754
766
|
cb.onComplete(result);
|
|
755
767
|
resolve();
|
|
756
768
|
};
|