@cartanova/qgrid-cli 2.3.2 → 2.3.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.
Files changed (26) hide show
  1. package/bundle/dist/application/qgrid/qgrid.dispatcher.js +91 -278
  2. package/bundle/dist/application/qgrid/qgrid.frame.js +15 -5
  3. package/bundle/dist/utils/providers/anthropic/anthropic-constants.js +33 -12
  4. package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +39 -96
  5. package/bundle/dist/utils/providers/anthropic/claude-session.js +47 -32
  6. package/bundle/dist/utils/providers/anthropic/stream-json-adapter.js +3 -36
  7. package/bundle/dist/utils/providers/common/model-cost.js +3 -2
  8. package/bundle/dist/utils/providers/openai/codex-worker.js +1 -2
  9. package/bundle/dist/utils/providers/openai/openai-dispatcher.js +19 -12
  10. package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +193 -6
  11. package/bundle/src/application/qgrid/qgrid.dispatcher.ts +140 -364
  12. package/bundle/src/application/qgrid/qgrid.frame.ts +38 -23
  13. package/bundle/src/utils/providers/anthropic/anthropic-constants.test.ts +60 -0
  14. package/bundle/src/utils/providers/anthropic/anthropic-constants.ts +57 -16
  15. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.test.ts +92 -340
  16. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.ts +50 -185
  17. package/bundle/src/utils/providers/anthropic/claude-session.test.ts +71 -62
  18. package/bundle/src/utils/providers/anthropic/claude-session.ts +56 -46
  19. package/bundle/src/utils/providers/anthropic/stream-json-adapter.test.ts +19 -30
  20. package/bundle/src/utils/providers/anthropic/stream-json-adapter.ts +24 -101
  21. package/bundle/src/utils/providers/common/model-cost.test.ts +19 -1
  22. package/bundle/src/utils/providers/common/model-cost.ts +2 -1
  23. package/bundle/src/utils/providers/common/provider-dispatcher.ts +7 -4
  24. package/bundle/src/utils/providers/openai/codex-worker.ts +2 -8
  25. package/bundle/src/utils/providers/openai/openai-dispatcher.ts +18 -11
  26. package/package.json +1 -1
@@ -0,0 +1,60 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ ANTHROPIC_DEFAULT_MODEL,
5
+ assertSupportedOneMillionSuffix,
6
+ canonicalAnthropicModel,
7
+ hasOneMillionSuffix,
8
+ needsCli1mSuffix,
9
+ supports1MContext,
10
+ } from "./anthropic-constants";
11
+
12
+ describe("canonicalAnthropicModel", () => {
13
+ it("미지정이면 default model", () => {
14
+ expect(canonicalAnthropicModel()).toBe(ANTHROPIC_DEFAULT_MODEL);
15
+ });
16
+
17
+ it("provider prefix 를 제거한다", () => {
18
+ expect(canonicalAnthropicModel("anthropic/claude-opus-4-8")).toBe("claude-opus-4-8");
19
+ });
20
+
21
+ it("[1m] suffix 를 base canonical 에서 제거한다", () => {
22
+ expect(canonicalAnthropicModel("claude-sonnet-4-6[1m]")).toBe("claude-sonnet-4-6");
23
+ expect(canonicalAnthropicModel("anthropic/claude-sonnet-4-6[1m]")).toBe("claude-sonnet-4-6");
24
+ });
25
+ });
26
+
27
+ describe("Anthropic 1M context policy", () => {
28
+ it("지원 모델은 실측 확인된 exact set 만 true", () => {
29
+ expect(supports1MContext("claude-sonnet-4-6")).toBe(true);
30
+ expect(supports1MContext("claude-opus-4-6")).toBe(true);
31
+ expect(supports1MContext("claude-opus-4-8")).toBe(true);
32
+
33
+ expect(supports1MContext("claude-opus-4-7")).toBe(false);
34
+ expect(supports1MContext("claude-sonnet-4-5")).toBe(false);
35
+ expect(supports1MContext("claude-sonnet-4")).toBe(false);
36
+ expect(supports1MContext("claude-haiku-4-5")).toBe(false);
37
+ });
38
+
39
+ it("CLI suffix 필요 모델과 기본 1M 모델을 분리한다", () => {
40
+ expect(needsCli1mSuffix("claude-sonnet-4-6")).toBe(true);
41
+ expect(needsCli1mSuffix("claude-opus-4-6")).toBe(true);
42
+ expect(needsCli1mSuffix("claude-opus-4-8")).toBe(false);
43
+ expect(needsCli1mSuffix("claude-opus-4-7")).toBe(false);
44
+ });
45
+
46
+ it("provider prefix 와 [1m] suffix 가 묻어도 base 기준으로 판별한다", () => {
47
+ expect(supports1MContext("anthropic/claude-sonnet-4-6[1m]")).toBe(true);
48
+ expect(needsCli1mSuffix("anthropic/claude-sonnet-4-6[1m]")).toBe(true);
49
+ expect(supports1MContext("anthropic/claude-opus-4-8[1m]")).toBe(true);
50
+ expect(needsCli1mSuffix("anthropic/claude-opus-4-8[1m]")).toBe(false);
51
+ });
52
+
53
+ it("unsupported alias + [1m] 은 조용히 다운그레이드하지 않는다", () => {
54
+ expect(hasOneMillionSuffix("sonnet[1m]")).toBe(true);
55
+ expect(() => assertSupportedOneMillionSuffix("sonnet[1m]")).toThrow(
56
+ /Unsupported Anthropic 1M model suffix/,
57
+ );
58
+ expect(() => assertSupportedOneMillionSuffix("anthropic/claude-sonnet-4-6[1m]")).not.toThrow();
59
+ });
60
+ });
@@ -1,31 +1,25 @@
1
1
  /**
2
2
  * Anthropic(Claude) provider 상수.
3
3
  *
4
- * 기존 qgrid.dispatcher.ts executeClaude 플래그/env 정합을 맞추되, 멀티턴용으로 조정한다:
4
+ * Claude Code CLI 플래그/env 계약을 Anthropic dispatcher 용으로 고정한다:
5
5
  * - `--no-session-persistence` 제거 (멀티턴 필수).
6
6
  * - `--input-format stream-json` 추가 (stdin 으로 입력 어댑터 JSONL 흘림).
7
- * - `--session-id` / `--resume` 세션 소유.
7
+ * - Anthropic 경로는 매 호출 `--session-id` fresh session 발급한다.
8
8
  */
9
9
 
10
10
  // CC --model 플래그가 받는 official model id (alias "sonnet" 가 아니라 정확한 식별자로 고정).
11
11
  // claude-api 스킬 확인: Sonnet 4.6 official id = "claude-sonnet-4-6" (날짜 suffix 없음).
12
- // model-cost 테이블에도 "claude-sonnet-4-6" 키 존재. (owner 지시)
12
+ // model-cost 테이블에도 "claude-sonnet-4-6" 키 존재
13
13
  export const ANTHROPIC_DEFAULT_MODEL = "claude-sonnet-4-6";
14
- export const ANTHROPIC_DEFAULT_EFFORT = "high";
15
-
16
- // model id 를 canonical(provider prefix 없는 CLI/cost 키) 로 정규화한다.
17
- // ai-sdk 는 'anthropic/claude-*' 형태로 보내는데, prefix 가 compatibilityKey/calculateCostUsd 에
18
- // 새면 호환키가 갈리거나 cost 가 default 단가로 오계산된다. 미지정이면 default.
19
- export function canonicalAnthropicModel(model?: string): string {
20
- const raw = model || ANTHROPIC_DEFAULT_MODEL;
21
- return raw.includes("/") ? raw.split("/").pop()! : raw;
22
- }
23
-
14
+ // OpenAI provider 기본값(openai-dispatcher DEFAULT_EFFORT)과 맞춘다. 클라가 effort 미지정 시 안전망.
15
+ export const ANTHROPIC_DEFAULT_EFFORT = "low";
24
16
  // project scope cwd (settings.json 격리). 토큰별 격리는 CLAUDE_CONFIG_DIR 로 한다.
25
17
  export const ANTHROPIC_CLAUDE_CWD = "/tmp/qgrid-anthropic";
26
-
27
18
  // per-token CLAUDE_CONFIG_DIR 의 베이스. 실제 경로는 `${BASE}/${tokenId}` (R10 격리).
28
19
  export const ANTHROPIC_CONFIG_DIR_BASE = "/tmp/qgrid-anthropic-config";
20
+ // CC 가 자동 활성화하는 deferred 도구 — 토큰 최적화 위해 dispatcher 경로에서 차단.
21
+ export const ANTHROPIC_DISALLOWED_TOOLS = ["Monitor", "PushNotification", "RemoteTrigger"] as const;
22
+ const ONE_MILLION_SUFFIX_RE = /\[1m\]$/i;
29
23
 
30
24
  // 토큰별 CLAUDE_CONFIG_DIR 경로 규칙(R10 격리 계약). tokenId 별로 분리되어, 같은 claude session-id
31
25
  // 라도 다른 토큰이면 다른 config dir → transcript 가 섞이지 않는다. 부수효과 없는 순수 함수라
@@ -34,5 +28,52 @@ export function anthropicConfigDir(tokenId: number): string {
34
28
  return `${ANTHROPIC_CONFIG_DIR_BASE}/${tokenId}`;
35
29
  }
36
30
 
37
- // CC 자동 활성화하는 deferred 도구 토큰 최적화 위해 차단(기존 executeClaude 와 동일).
38
- export const ANTHROPIC_DISALLOWED_TOOLS = ["Monitor", "PushNotification", "RemoteTrigger"] as const;
31
+ // model id canonical(provider prefix 없는 CLI/cost 키) 정규화
32
+ // ai-sdk 'anthropic/claude-*' 형태로 보내는데, prefix calculateCostUsd 에 새면 cost 가
33
+ // default 단가로 오계산된다. `[1m]` suffix 도 CLI emit 전용이라
34
+ // base canonical 에서는 제거한다. 미지정이면 default.
35
+ export function canonicalAnthropicModel(model?: string): string {
36
+ const raw = model || ANTHROPIC_DEFAULT_MODEL;
37
+ const withoutProvider = raw.includes("/") ? raw.split("/").pop()! : raw;
38
+ return withoutProvider.replace(ONE_MILLION_SUFFIX_RE, "");
39
+ }
40
+
41
+ export function hasOneMillionSuffix(model?: string): boolean {
42
+ return model !== undefined && ONE_MILLION_SUFFIX_RE.test(model);
43
+ }
44
+
45
+ // 1M 지원 여부는 Claude Code 유출본의 prefix 판정이 아니라 qgrid 실측 기준 exact set 으로 고정한다.
46
+ // opus-4-7 은 실측 전까지 미포함. 새 모델 추가 시 실제 claude CLI contextWindow 동작으로 확인한다.
47
+ const ONE_MILLION_CONTEXT_MODELS = new Set([
48
+ "claude-sonnet-4-6",
49
+ "claude-opus-4-6",
50
+ "claude-opus-4-8",
51
+ ]);
52
+
53
+ const CLI_ONE_MILLION_SUFFIX_MODELS = new Set(["claude-sonnet-4-6", "claude-opus-4-6"]);
54
+
55
+ // 불변 계약: suffix 가 필요한 모델은 반드시 1M 지원 모델의 부분집합이어야 한다. 역방향 불일치(suffix
56
+ // 대상인데 지원 집합엔 없음)면 needsCli1mSuffix=true·supports1MContext=false 가 동시에 나서
57
+ // `--model [1m]` 과 DISABLE_1M env 가 모순되게 붙는다. 새 모델을 한쪽 Set 에만 넣는 실수를 모듈
58
+ // 로드 시점에 잡는다.
59
+ for (const model of CLI_ONE_MILLION_SUFFIX_MODELS) {
60
+ if (!ONE_MILLION_CONTEXT_MODELS.has(model)) {
61
+ throw new Error(`[invariant] CLI 1M suffix model not in context set: ${model}`);
62
+ }
63
+ }
64
+
65
+ export function supports1MContext(model?: string): boolean {
66
+ return ONE_MILLION_CONTEXT_MODELS.has(canonicalAnthropicModel(model));
67
+ }
68
+
69
+ export function needsCli1mSuffix(model?: string): boolean {
70
+ return CLI_ONE_MILLION_SUFFIX_MODELS.has(canonicalAnthropicModel(model));
71
+ }
72
+
73
+ export function assertSupportedOneMillionSuffix(model?: string): void {
74
+ if (!hasOneMillionSuffix(model)) return;
75
+ const canonical = canonicalAnthropicModel(model);
76
+ if (!supports1MContext(canonical)) {
77
+ throw new Error(`Unsupported Anthropic 1M model suffix: ${model}`);
78
+ }
79
+ }