@cartanova/qgrid-cli 2.4.2 → 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.
@@ -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
  };