@cartanova/qgrid-cli 2.9.0 → 2.9.2

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.
Files changed (31) hide show
  1. package/bundle/dist/startup-migrations.js +46 -7
  2. package/bundle/dist/utils/providers/anthropic/claude-session.js +4 -3
  3. package/bundle/dist/utils/providers/common/effort.js +51 -0
  4. package/bundle/dist/utils/providers/common/model-cost.js +9 -2
  5. package/bundle/dist/utils/providers/openai/openai-backend-protocol.js +1 -1
  6. package/bundle/dist/utils/providers/openai/openai-dispatcher.js +5 -3
  7. package/bundle/src/startup-migrations.test.ts +131 -14
  8. package/bundle/src/startup-migrations.ts +80 -5
  9. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.test.ts +1 -1
  10. package/bundle/src/utils/providers/anthropic/claude-session.test.ts +13 -0
  11. package/bundle/src/utils/providers/anthropic/claude-session.ts +3 -1
  12. package/bundle/src/utils/providers/common/effort.test.ts +53 -0
  13. package/bundle/src/utils/providers/common/effort.ts +53 -0
  14. package/bundle/src/utils/providers/common/model-cost.ts +16 -1
  15. package/bundle/src/utils/providers/openai/openai-backend-protocol.ts +3 -1
  16. package/bundle/src/utils/providers/openai/openai-dispatcher.test.ts +20 -0
  17. package/bundle/src/utils/providers/openai/openai-dispatcher.ts +5 -4
  18. package/bundle/web-dist/client/assets/{index-DDSQ_6GL.js → index-DH1n8kgG.js} +3 -3
  19. package/bundle/web-dist/client/assets/{logs-D2OMhBlx.js → logs-D36O-NET.js} +1 -1
  20. package/bundle/web-dist/client/assets/{monit-CYLYHrc-.js → monit-DhZAaz9K.js} +1 -1
  21. package/bundle/web-dist/client/assets/{routes-DlG0sJHR.js → routes-C7q1CsSv.js} +1 -1
  22. package/bundle/web-dist/client/assets/{settings-BeCBndh_.js → settings-M_jfoIAe.js} +1 -1
  23. package/bundle/web-dist/client/assets/{show-BdmzN1As.js → show-CORjMGQs.js} +1 -1
  24. package/bundle/web-dist/client/assets/{tokens-KCfMOeTH.js → tokens-C7-j4VXD.js} +1 -1
  25. package/bundle/web-dist/client/assets/{use-oauth-login-flow-2dSjIvBk.js → use-oauth-login-flow-Fi62xbBc.js} +1 -1
  26. package/bundle/web-dist/client/index.html +1 -1
  27. package/bundle/web-dist/server/entry-server.generated.js +1 -1
  28. package/package.json +1 -1
  29. package/skills/qgrid/references/ai-sdk-provider-contract.md +2 -2
  30. package/skills/qgrid/references/cli-env-and-server-boot.md +4 -1
  31. package/skills/qgrid/references/provider-runtime-differences.md +2 -0
@@ -0,0 +1,53 @@
1
+ import { getLogger } from "@logtape/logtape";
2
+
3
+ import { openaiModelMaxEffort } from "./model-cost";
4
+
5
+ const logger = getLogger(["qgrid", "effort"]);
6
+
7
+ /**
8
+ * provider 별 reasoning effort 어휘.
9
+ *
10
+ * SDK 가 보낸 effort 문자열을 그대로 provider 에 넘기지 않는다. provider 마다 받는 값이 다르고,
11
+ * 모르는 값을 받았을 때의 반응도 다르다(Codex 백엔드는 400 이 예상되고, Claude Code 는 경고만
12
+ * 남기고 모델 기본 effort 로 실행한다). 각 provider dispatcher 가 요청을 만들 때 여기서 판정해,
13
+ * 맞지 않는 값은 오류 없이 버리고 "미지정" 과 같게 취급한다.
14
+ */
15
+
16
+ // Codex 구독 백엔드 모델 카탈로그(~/.codex/models_cache.json) 의 supported_reasoning_levels 합집합.
17
+ // 공개 OpenAI API 의 `none`/`minimal` 은 이 경로에 없다. 모델별 상한은 model-cost.ts 의 카탈로그에 있다.
18
+ export const OPENAI_EFFORTS = ["low", "medium", "high", "xhigh", "max", "ultra"] as const;
19
+ export type OpenAIEffort = (typeof OPENAI_EFFORTS)[number];
20
+
21
+ // Claude Code `--effort` 허용값. 모델별 상한(xhigh_effort / max_effort capability)은 Claude Code 가
22
+ // 스스로 처리하므로 qgrid 는 provider 집합만 검사한다.
23
+ export const ANTHROPIC_EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const;
24
+ export type AnthropicEffort = (typeof ANTHROPIC_EFFORTS)[number];
25
+
26
+ // 어휘 안에 있고 상한(있다면) 이하인 값만 통과시킨다. 나머지는 debug 로그 후 미지정.
27
+ function resolveWithin<T extends string>(
28
+ levels: readonly T[],
29
+ effort: string | undefined,
30
+ max: T | undefined,
31
+ target: string,
32
+ ): T | undefined {
33
+ if (effort === undefined) return undefined;
34
+ const rank = levels.indexOf(effort as T);
35
+ if (rank < 0) {
36
+ logger.debug(`ignoring effort "${effort}" for ${target}: not in ${levels.join("/")}`);
37
+ return undefined;
38
+ }
39
+ if (max !== undefined && rank > levels.indexOf(max)) {
40
+ logger.debug(`ignoring effort "${effort}" for ${target}: model supports up to "${max}"`);
41
+ return undefined;
42
+ }
43
+ return effort as T;
44
+ }
45
+
46
+ /** canonical OpenAI model(`openai/` prefix 제거 후) 기준으로 effort 를 판정한다. */
47
+ export function resolveOpenAIEffort(model: string, effort?: string): OpenAIEffort | undefined {
48
+ return resolveWithin(OPENAI_EFFORTS, effort, openaiModelMaxEffort(model), `openai/${model}`);
49
+ }
50
+
51
+ export function resolveAnthropicEffort(effort?: string): AnthropicEffort | undefined {
52
+ return resolveWithin(ANTHROPIC_EFFORTS, effort, undefined, "anthropic");
53
+ }
@@ -10,6 +10,8 @@
10
10
  * @see https://platform.claude.com/docs/en/about-claude/pricing
11
11
  */
12
12
 
13
+ import { type OpenAIEffort } from "./effort";
14
+
13
15
  export interface ModelCosts {
14
16
  inputTokens: number;
15
17
  outputTokens: number;
@@ -45,13 +47,19 @@ const LONG_CONTEXT_272K: NonNullable<ModelCosts["longContext"]> = {
45
47
  outputMultiplier: 1.5,
46
48
  };
47
49
 
48
- const OPENAI_COSTS: Record<string, ModelCosts> = {
50
+ // OpenAI 모델 카탈로그. 단가와 함께 Codex 백엔드가 모델별로 받는 최대 reasoning effort 를 한 곳에 둔다.
51
+ // maxEffort 출처: ~/.codex/models_cache.json 의 supported_reasoning_levels (2026-09-02 확인).
52
+ // 미기재 모델은 xhigh 까지만 받는다.
53
+ type OpenAIModelSpec = ModelCosts & { maxEffort?: OpenAIEffort };
54
+
55
+ const OPENAI_COSTS: Record<string, OpenAIModelSpec> = {
49
56
  // GPT-5.6 Sol, Terra, Luna. cache write 단가는 외부 logger/manual usage 입력을 위해
50
57
  // 유지한다. 현재 OpenAI 응답 usage 에 cache write 토큰 필드가 없으면 비용은 0으로 계산된다.
51
58
  // @see https://developers.openai.com/api/docs/models/gpt-5.6-sol
52
59
  // @see https://developers.openai.com/api/docs/models/gpt-5.6-terra
53
60
  // @see https://developers.openai.com/api/docs/models/gpt-5.6-luna
54
61
  "gpt-5.6-sol": {
62
+ maxEffort: "ultra",
55
63
  inputTokens: 5,
56
64
  outputTokens: 30,
57
65
  cachedInputTokens: 0.5,
@@ -59,6 +67,7 @@ const OPENAI_COSTS: Record<string, ModelCosts> = {
59
67
  longContext: LONG_CONTEXT_272K,
60
68
  },
61
69
  "gpt-5.6-terra": {
70
+ maxEffort: "ultra",
62
71
  inputTokens: 2.5,
63
72
  outputTokens: 15,
64
73
  cachedInputTokens: 0.25,
@@ -66,6 +75,7 @@ const OPENAI_COSTS: Record<string, ModelCosts> = {
66
75
  longContext: LONG_CONTEXT_272K,
67
76
  },
68
77
  "gpt-5.6-luna": {
78
+ maxEffort: "max",
69
79
  inputTokens: 1,
70
80
  outputTokens: 6,
71
81
  cachedInputTokens: 0.1,
@@ -148,6 +158,11 @@ const ANTHROPIC_COSTS: Record<string, ModelCosts> = {
148
158
  // @see https://help.openai.com/en/articles/20001106-codex-rate-card
149
159
  const DEFAULT_COSTS: ModelCosts = { inputTokens: 3, outputTokens: 15, cachedInputTokens: 0.3 };
150
160
 
161
+ /** Codex 백엔드가 이 모델에 허용하는 최대 effort. 카탈로그에 없는 모델은 xhigh 까지다. */
162
+ export function openaiModelMaxEffort(model: string): OpenAIEffort {
163
+ return OPENAI_COSTS[model]?.maxEffort ?? "xhigh";
164
+ }
165
+
151
166
  export function getModelCosts(model: string): ModelCosts {
152
167
  const normalizedModel = (model.split("/").pop() ?? model).replace(/\[1m\]$/i, "");
153
168
  return OPENAI_COSTS[normalizedModel] ?? ANTHROPIC_COSTS[normalizedModel] ?? DEFAULT_COSTS;
@@ -1,5 +1,7 @@
1
1
  import os from "node:os";
2
2
 
3
+ import { type OpenAIEffort } from "../common/effort";
4
+
3
5
  export const CHATGPT_CODEX_RESPONSES_URL =
4
6
  "https://chatgpt.com/backend-api/codex/responses" as const;
5
7
 
@@ -58,7 +60,7 @@ export interface OpenAIResponsesOptions {
58
60
  toolChoice?: string | OpenAIResponseItem;
59
61
  parallelToolCalls?: boolean;
60
62
  reasoning?: {
61
- effort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
63
+ effort?: OpenAIEffort;
62
64
  summary?: "auto" | "concise" | "detailed";
63
65
  };
64
66
  verbosity?: "low" | "medium" | "high";
@@ -416,3 +416,23 @@ describe("OpenAIDispatcher direct runtime", () => {
416
416
  await expect(blocked.generate(request())).rejects.toThrow("NO_OPENAI_WORKERS");
417
417
  });
418
418
  });
419
+
420
+ describe("effort 어휘 해석 (Codex 카탈로그 기준)", () => {
421
+ async function mappedEffortFor(model: string, effort: string): Promise<unknown> {
422
+ let mapped: OpenAIResponsesOptions | undefined;
423
+ const d = dispatcher((options) => {
424
+ mapped = options;
425
+ return events({ type: "completed", responseId: "r1" });
426
+ });
427
+ await d.onTokenAdded(7, "primary", credentials, null, 1);
428
+ await d.generate(request({ model, effort }));
429
+ return mapped?.reasoning?.effort;
430
+ }
431
+
432
+ it("모델이 지원하는 값은 그대로 보내고, 상한 초과·공개 API 어휘는 reasoning 에서 빼 백엔드 기본값을 쓴다", async () => {
433
+ await expect(mappedEffortFor("gpt-5.6-terra", "ultra")).resolves.toBe("ultra");
434
+ await expect(mappedEffortFor("gpt-5.5", "xhigh")).resolves.toBe("xhigh");
435
+ await expect(mappedEffortFor("gpt-5.5", "max")).resolves.toBeUndefined();
436
+ await expect(mappedEffortFor("gpt-5.6-terra", "minimal")).resolves.toBeUndefined();
437
+ });
438
+ });
@@ -3,6 +3,7 @@ import { getLogger } from "@logtape/logtape";
3
3
  import { QuotaThresholdExceededError } from "../../../application/qgrid/qgrid.types";
4
4
  import { TokenModel } from "../../../application/token/token.model";
5
5
  import { type OpenAICredentials } from "../../../application/token/token.types";
6
+ import { resolveOpenAIEffort } from "../common/effort";
6
7
  import {
7
8
  type GenerateRequest,
8
9
  type GenerateResult,
@@ -97,11 +98,11 @@ function inputItems(req: GenerateRequest): OpenAIResponseItem[] {
97
98
  function asReasoning(req: GenerateRequest): OpenAIResponsesOptions["reasoning"] | undefined {
98
99
  const summary =
99
100
  req.reasoningSummary && req.reasoningSummary !== "none" ? req.reasoningSummary : undefined;
100
- if (!req.effort && !summary) return undefined;
101
+ // Codex 어휘·모델 상한 밖의 effort 는 여기서 조용히 버린다(백엔드 기본값 적용).
102
+ const effort = resolveOpenAIEffort(req.model ?? "", req.effort);
103
+ if (!effort && !summary) return undefined;
101
104
  return {
102
- ...(req.effort
103
- ? { effort: req.effort as NonNullable<OpenAIResponsesOptions["reasoning"]>["effort"] }
104
- : {}),
105
+ ...(effort ? { effort } : {}),
105
106
  ...(summary
106
107
  ? {
107
108
  summary: summary as NonNullable<OpenAIResponsesOptions["reasoning"]>["summary"],