Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions devlog/_plan/260911_deepseek_v41_transition/000_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 260911 — DeepSeek V4.1 전환

DeepSeek가 2026-09-10에 V4.1-Flash를 내면서 V4 계열의 이름이 한 번에 움직였다. `deepseek-v4-flash`와 `deepseek-v4-flash-vision-exp`는 모델로서 은퇴하고 이름만 V4.1-Flash로 라우팅되는 별칭이 됐고, `deepseek-v4-pro`는 2026-09-14 04:00 UTC부터 단계적으로 퇴역하며 그 시점부터 요청이 V4.1-Flash로 넘어간다. opencodex는 이 두 id를 13개 프로바이더 프리셋에 손으로 박아두고 있어서, 그대로 두면 Pro 컨텍스트 창과 Pro 가격을 광고하면서 실제로는 Flash를 서빙하는 상태가 된다. 이 유닛은 V4.1을 전개하고 v4-pro를 걷어내고, 같은 영역을 건드리는 기여자 PR을 먼저 정리한 뒤 둘 다 dev에 머지한다. 바뀌는 사람은 DeepSeek 경로를 쓰는 모든 사용자다.

근거는 `001_evidence.md`, 출현 지점 집계는 `002_inventory.md`에 있다.

## 루프 스펙

| 항목 | 내용 |
| --- | --- |
| Loop archetype | satisfy-spec |
| Trigger | 사용자 지시: v4.1-flash를 v4-flash가 있는 모든 곳에 전개하고, 퇴역한 v4-pro를 전부 제거하고, PR #4258과 #4274를 머지하라 |
| Goal | V4.1 전개 + v4-pro 제거가 focused 테스트와 함께 dev에 머지되고, #4258/#4274도 머지된다 |
| Non-goals | 새 사용자 config 필드, 어댑터 와이어 동작 변경, main/preview 승격, 릴리스, 생성 메타데이터 수작업 편집 |
| Verifier | `bun test` 영향 도메인, `bun run typecheck`, `bun run privacy:scan`, 머지 전 exact-head CI |
| Stop condition | 두 PR과 이번 변경이 dev에 머지된 시점 |
| Memory artifact | `devlog/_plan/260911_deepseek_v41_transition/` |
| Expected terminal outcomes | DONE = 머지 완료. BLOCKED = CI가 이 변경과 무관한 이유로 반복 실패하거나 머지 권한이 거부될 때 |
| Escalation condition | 사용자가 머지를 명시 승인했다. main/preview 승격과 릴리스는 별도 승인 필요 |
| Resource bounds | 쓰기 범위: `src/`, `tests/`, `docs-site/`, 이 플랜 유닛. 전체 스위트는 사용자 지시로 로컬에서 돌리지 않고 CI에 위임한다 |

## 작업 단계 지도

| work-phase | 문서 | 내용 |
| --- | --- | --- |
| wp1 | 000-002 | 근거·인벤토리·로드맵 잠금 (docs only) |
| wp2 | `010_phase1_pr4258.md` | 기여자 PR #4258 리뷰와 머지 |
| wp3 | `020_phase2_v41_rollout.md` | V4.1-Flash 전개 |
| wp4 | `030_phase3_v4pro_removal.md` | v4-pro 퇴역 제거 |
| wp5 | `040_phase4_merge.md` | docs-site 동기화, PR 게시와 머지 |

## 이 유닛이 내린 두 가지 판단

**1. id는 프로바이더별로 다르다.** DeepSeek 1st-party API의 공식 id는 `deepseek-flash`다. 게이트웨이가 노출하는 철자는 `deepseek-v4.1-flash`이고, 이건 이슈 #4253과 PR #4258이 저장소 안에서 확인해 준 사실이다. "모든 곳에 같은 id"로 넣으면 네이티브 쪽이 틀린 id를 갖는다.

**2. 벤더 호스팅 스냅샷은 DeepSeek 수명주기와 별개다.** Volcengine Ark는 `deepseek-v4-pro-260425`처럼 날짜가 박힌 스냅샷을 고정하고, Alibaba·Ollama Cloud·NVIDIA NIM·Baseten도 각자 로스터를 따로 발표한다. DeepSeek 1st-party 퇴역 공지가 그 벤더들의 배포까지 끝내지는 않는다. 그래서 제거는 **DeepSeek 1st-party와 그것을 되파는 Zen 계열을 먼저** 확정하고, 벤더 호스팅 프리셋은 같은 커밋에서 분리해 PR 본문에 근거와 함께 드러낸다 — 리뷰어가 한 커밋만 떼어낼 수 있게.

## wp1 감사 반영 (2026-09-11)

독립 감사가 로드맵 초안의 결함 6건을 잡았고 전부 수용했다. 가장 큰 것 둘:

- 초안은 공유 상수 `DEEPSEEK_THINKING_MODELS`에 V4.1을 넣으려 했는데, 그 상수는 `deepseek` 1st-party 프리셋의 `models:` 배열 자체를 포함해 6개 프리셋 21곳이 소비한다(`registry.ts:2045`). 그대로 하면 게이트웨이 철자가 네이티브 프리셋으로 새서 020의 수용기준이 자기모순이 된다. 상수를 분리하는 설계로 다시 썼다.
- 초안의 "Pro 사다리를 광고한다"는 근거가 없다. `DEEPSEEK_PRO_*`와 `DEEPSEEK_FLASH_*` 효율 맵은 값이 같다(`registry.ts:701-715`). 실제로 어긋나는 건 **컨텍스트 창과 가격**이다.

나머지는 002/020/030의 해당 절에 반영했다.
25 changes: 25 additions & 0 deletions devlog/_plan/260911_deepseek_v41_transition/001_evidence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 001 — 근거

2026-09-11 웹 조사. 출처는 DeepSeek 공식 API 문서와 9/10 공지.

## 확인된 사실

| 사실 | 출처 |
| --- | --- |
| V4.1-Flash 출시 2026-09-10 | <https://api-docs.deepseek.com/news/news260910/> |
| 공식 API id는 `deepseek-flash` | <https://api-docs.deepseek.com/> |
| `deepseek-v4-flash`와 `deepseek-v4-flash-vision-exp`는 모델로서 은퇴, 이름은 V4.1-Flash로 라우팅되는 호환 별칭으로 유지, Flash 가격 과금 | <https://api-docs.deepseek.com/> |
| `deepseek-v4-pro`는 2026-09-14 04:00 UTC부터 단계적 퇴역, 이후 요청은 V4.1-Flash로 자동 라우팅, 신규 연동은 `deepseek-flash` 권고 | <https://api-docs.deepseek.com/news/news260910/> |

## 기록해 두는 불일치

같은 체인지로그를 근거로, 질의 표현에 따라 상반된 요약이 돌아왔다. 한쪽은 위 표대로 v4-pro 퇴역과 Flash 요금 적용을 말했고, 다른 쪽은 "9월 14일 이후에도 서비스 계속, 과금 변동 없음, 7월 24일 퇴역한 건 `deepseek-chat`/`deepseek-reasoner`"라고 답했다.

이 유닛은 전자를 따른다. 다만 두 해석이 공통으로 인정하는 사실 하나만으로도 변경 근거는 충분하다: **9월 14일부터 `deepseek-v4-pro` 요청은 V4.1-Flash로 라우팅된다.** 퇴역이냐 임시 라우팅이냐와 무관하게, 그 시점 이후 `deepseek-v4-pro` 행은 Pro 사다리·Pro 컨텍스트·Pro 가격을 광고하면서 Flash를 서빙한다. 잘못된 광고를 남겨두는 쪽이 제거보다 나쁘다.

저장소 내부 근거로는 이슈 #4253과 PR #4258이 Command Code 라이브 로스터에서 `deepseek/deepseek-v4.1-flash`가 실제로 서빙되는 것을 확인해 준다.

## 이 유닛이 주장하지 않는 것

- 벤더 호스팅(Volcengine, Alibaba, Ollama Cloud, NVIDIA NIM, Baseten, cline-pass, orcarouter, codebuddy, qoder) 로스터에서 v4-pro가 중단됐다는 주장은 **하지 않는다**. 그쪽은 각자 스냅샷과 일정이 있고, Volcengine은 `deepseek-v4-pro-260425`처럼 날짜가 박힌 id를 쓴다.
- Zen 게이트웨이가 `deepseek-flash` 철자를 받는다는 주장도 하지 않는다. 게이트웨이 쪽은 관측된 `deepseek-v4.1-flash`를 쓴다.
49 changes: 49 additions & 0 deletions devlog/_plan/260911_deepseek_v41_transition/002_inventory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# 002 — 출현 지점 집계

`rg` 기준, 2026-09-11 브랜치 `codex/260911-opencode-go-free-stabilization`.

| id | 파일 수 | 히트 수 |
| --- | --- | --- |
| `deepseek-v4-pro` | 62 | 293 |
| `deepseek-v4-flash` | 99 | 585 |

## `DEEPSEEK_THINKING_MODELS` 소비처 (감사 정정)

이 상수(`registry.ts:619`)는 Zen 3종만 먹이는 게 아니다. **6개 프리셋 21곳**이 소비하며, 그중에는 `deepseek` 1st-party 프리셋의 `models:` 배열 자체가 포함된다.

| 프리셋 | 앵커 |
| --- | --- |
| `opencode-go` | 1760, 1768, 1776, 1803, 1813 |
| `deepseek` 1st-party | **2045 (`models:` spread)**, 2114-2121 |
| `alibaba-token-plan` | 2813-2818 |
| `opencode-zen` | 3047-3064 |
| `opencode-free` | 3108 |

이것 때문에 "공유 상수에 V4.1을 추가" 설계는 성립하지 않는다. 020이 상수 분리로 다시 설계됐다.

## v4-pro를 선언하는 프로바이더 (registry.ts)

| 프로바이더 | 성격 | 앵커 |
| --- | --- | --- |
| `deepseek` (1st-party) | **DeepSeek 직접** | 2038-2078 (`modelContextWindows`, `modelWireDefaults`, `modelResponsesTerminalRepair`) |
| `opencode-go` / `opencode-zen` / `opencode-free` | Zen 게이트웨이가 DeepSeek을 되팜 | 619 `DEEPSEEK_THINKING_MODELS`, 1793 |
| `command-code` (OAuth + API key) | 게이트웨이 | 631, 1180-1190, 2305 |
| `alibaba-token-plan` / `-intl` | 벤더 호스팅 | 736, 749, 758, 2832, 2857-2913 |
| `volcengine` ark / coding / agent | 벤더 호스팅, **날짜 스냅샷** `deepseek-v4-pro-260425` | 791, 807, 816, 838, 850, 2785, 2791 |
| `ollama` cloud | 벤더 호스팅 | 2951, 2963 |
| `nvidia-nim` | 벤더 호스팅 | 969 |
| `baseten` | 벤더 호스팅 (`deepseek-ai/DeepSeek-V4-Pro`) | 1010-1059 |
| `cline-pass` | 게이트웨이 | 1144, 1199 |
| `orcarouter` | 게이트웨이 | 1180-1190 |
| `codebuddy` / `qoder` | 게이트웨이 | `codebuddy-models.ts`, `qoder-models.ts` |

## 손대지 않는 영역과 이유

| 영역 | 이유 |
| --- | --- |
| `scripts/model-metadata.source.json` (47건), `src/generated/model-metadata.ts` (3건) | 벤더 스냅샷에서 **생성되는** 파일이다. 손으로 지우면 다음 생성에서 되돌아온다. 게다가 `src/usage/cost.ts`가 과거 요청 비용을 이 표로 계산하므로, 행을 지우면 이미 기록된 사용량의 원가가 깨진다 |
| 임의 fixture id로 v4-pro를 쓰는 테스트 | 레지스트리 멤버십을 주장하지 않는 테스트는 모델 id를 문자열로만 쓴다. 깨지는 것만 고친다 |

## 테스트 영향 예상

감사 정정: 영향 파일은 5개가 아니라 **24개**다. 위 다섯 외에 `tests/routing/router.test.ts:450`(정확 목록), `tests/providers/orcarouter-provider.test.ts:139`, `tests/gui/alibaba-intl-token-plan.test.ts:31`, `tests/routing/fastwire-policy.test.ts`, `tests/codex-integration/slug-codec.test.ts`, `tests/server/adapter-resolve.test.ts` 등이 포함된다.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 010 — wp2: 기여자 PR #4258 리뷰와 머지

<https://github.com/lidge-jun/opencodex/pull/4258> · `gitgarmin` · base `dev` · head `codex/command-code-v41-qwen-efforts`

파일 2개: `src/providers/command-code-efforts.ts` (+23/-0), `tests/providers/command-code-provider.test.ts` (+33/-0).

## 왜 먼저인가

같은 파일을 wp3에서 건드린다. 기여자 PR을 먼저 넣고 그 위에 리베이스하는 게 순서다. 반대로 하면 기여자가 리베이스 부담을 진다.

## 리뷰 항목

1. 추가된 두 행(`deepseek/deepseek-v4.1-flash`, `Qwen/Qwen3.8-Flash`)이 `COMMAND_CODE_MODEL_EFFORTS` 조회 계약과 맞는가.
2. 사다리 값의 출처가 본문 주장과 일치하는가. 본문은 같은 패밀리 행에서 추론했다고 밝히고, 라이브 200 응답을 근거로 든다.
3. 신규 테스트가 케이스 폴딩과 두 프리셋(OAuth/API key)을 모두 고정하는가.
4. AGENTS.md 리뷰 규칙: base `dev` ✓, 보안 표면 미접촉, 테스트 동반.
5. CI가 exact head에서 green인가.

## 수용 기준

- 리뷰 코멘트가 영어로 남는다 (AGENTS.md 리뷰 규칙).
- exact-head CI green을 확인한 뒤 머지한다.
- 머지 후 `dev`를 받아 내 브랜치를 리베이스하고 충돌이 없음을 확인한다.

## 검증

```
gh pr checks 4258
gh pr view 4258 --json mergeStateStatus,reviewDecision
bun test tests/providers/command-code-provider.test.ts
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# 020 — wp3: V4.1-Flash 전개 (2차 감사 후 재설계)

## 두 번 틀렸던 지점

**1차 초안**: `DEEPSEEK_THINKING_MODELS`에 V4.1을 그냥 얹으려 했다. 그 상수는 `deepseek` 1st-party 프리셋의 `models:`를 포함해 6개 프리셋이 공유하므로, 게이트웨이 철자가 네이티브로 샌다.

**2차 초안**: 그래서 상수를 레거시 전용으로 고정하고 신규 id를 따로 넣으려 했다. 감사가 `fail`을 냈고 이유가 맞다 — `deepseek` 프리셋의 모델별 맵 **다섯 개**가 전부 그 상수에서 파생된다(`registry.ts:2121-2124, 2128`). 상수를 레거시로 묶으면 `deepseek-flash`는 사다리·요약·`reasoning_content` 리플레이·비전 차단을 **전부** 잃고 #78형 400이 재발한다.

## 확정 설계: 상수를 세 갈래로 파생시킨다

```ts
// 업스트림이 호환 별칭으로 유지하는 레거시 V4 id
const DEEPSEEK_V4_LEGACY_MODELS = ["deepseek-v4-pro", "deepseek-v4-flash"];
// DeepSeek 1st-party: 공식 id는 deepseek-flash
const DEEPSEEK_NATIVE_THINKING_MODELS = ["deepseek-flash", ...DEEPSEEK_V4_LEGACY_MODELS];
// Zen 게이트웨이가 노출하는 철자
const DEEPSEEK_GATEWAY_THINKING_MODELS = ["deepseek-v4.1-flash", ...DEEPSEEK_V4_LEGACY_MODELS];
```

기존 이름 `DEEPSEEK_THINKING_MODELS`는 `DEEPSEEK_V4_LEGACY_MODELS`로 바뀐다. 벤더 호스팅 프리셋(volcengine 플랜, alibaba)은 그 레거시 상수를 계속 쓴다 — 그쪽은 V4 스냅샷을 자기 일정으로 서빙한다.

## 파일 변경 지도

| 위치 | 변경 |
| --- | --- |
| `registry.ts:619` | 상수 3개로 재구성 |
| `registry.ts:2121-2124, 2128` (deepseek 프리셋) | 다섯 맵을 `DEEPSEEK_NATIVE_THINKING_MODELS`로 전환 |
| `registry.ts:2049` (`models:`) | 같은 상수로 전환 |
| `registry.ts:2053` | `defaultModel`을 `deepseek-flash`로 |
| `registry.ts:2062, 2078` | `modelContextWindows`·`modelWireDefaults`·`modelResponsesTerminalRepair`에 `deepseek-flash` 항목 추가 |
| `registry.ts:1760, 1768, 1776, 1803, 1813` (opencode-go) | `DEEPSEEK_GATEWAY_THINKING_MODELS`로 전환 |
| `registry.ts:1791` (go `noVisionModels`, 리터럴) | `deepseek-v4.1-flash` 추가 |
| `registry.ts:3053-3071` (opencode-zen) | 게이트웨이 상수로 전환. 이 프리셋엔 `modelSupportsReasoningSummaries` 필드 자체가 없다 — 새로 만들지 않는다 |
| `registry.ts:3115` (opencode-free `noJsonSchemaModels`) | 게이트웨이 상수로 전환 |
| `src/providers/default-aliases.ts:54` 앞 | `/^deepseek-v4\.1/ → "ds41"` 을 `/^deepseek-v4/` **앞**에 둔다(첫 매치 승리). `/^deepseek-flash/ → "dsf"` 는 위치 무관 |

**건드리지 않는 것**: `opencode-free`의 `noVisionModels`(`3111`)는 `OPENCODE_ZEN_TEXT_ONLY_MODELS` 참조라 여기에 넣으면 zen까지 오염된다. free는 원래 DeepSeek id를 이 목록에 갖고 있지 않으므로 그대로 둔다. `command-code`는 PR #4258 소유. 벤더 호스팅 9곳은 V4.1 서빙 근거가 없어 제외.

## 수용 기준

1. `deepseek` 프리셋에서 `deepseek-flash`가 사다리·효율맵·요약·replay·noVision **다섯 곳 모두**에 나타난다. 이게 2차 감사가 잡은 실패 지점이므로 테스트로 직접 관측한다.
2. `opencode-go`에서 `deepseek-v4.1-flash`가 같은 대우를 받는다.
3. **반대 증거**: `deepseek` 프리셋에 `deepseek-v4.1-flash`가 없고, Zen 프리셋에 `deepseek-flash`가 없다.
4. 벤더 호스팅 프리셋(volcengine coding plan)의 DeepSeek 목록은 변하지 않는다.
5. `deepseek` `defaultModel`이 `deepseek-flash`다.

## 갱신해야 하는 기존 테스트 (감사 열거)

`tests/providers/provider-registry-parity.test.ts`: `197`(deepseek preserveReasoningContentModels `toEqual`), `199-201`(deepseek noVisionModels `toEqual`), `309`(defaultModel), `73-80`(go noVision `toEqual`), `86-92`(3종 noJsonSchema `toEqual`), `1421-1453`(DeepSeek id 열거). `tests/providers/opencode-go-deepseek.test.ts:159-160`(noJsonSchema `toEqual`). `tests/codex-integration/reasoning-effort.test.ts:274`(동일 `toEqual`).

`parity:184`는 `toContain`이라 안전하고, `model-metadata-sync.test.ts`는 `scripts/model-metadata.source.json`만 입력으로 재생성·바이트 비교하므로 레지스트리 추가로 깨지지 않는다.

## 기록해 두는 부수 사실

`scripts/model-metadata.source.json`에 `deepseek-flash`와 `deepseek-v4.1-flash` 행이 모두 없어 두 id의 비용 추정이 빈다. 생성 파일은 손대지 않는 방침(002)이므로 다음 메타데이터 생성에서 채워진다. PR 본문에 명시한다.

`opencode-free`는 `liveModels: true`인데 게이트웨이 상수 전환이 `noJsonSchemaModels` 한 곳뿐이라 `deepseek-v4.1-flash`가 사다리와 replay를 받지 못한다. 기존 `deepseek-v4-pro`/`-flash`도 같은 비대칭이므로 신규 결함은 아니다. PR 본문에 한 줄 남긴다.

## 검증

```
bun test tests/providers/provider-registry-parity.test.ts
bun test tests/providers/opencode-go-deepseek.test.ts
bun test tests/providers/deepseek-reasoning-replay.test.ts
bun test tests/codex-integration/slug-codec.test.ts
bun run typecheck
```
Loading
Loading