# SDD: 다중 Provider Protocol Profile과 Native Anthropic Messages

## 위치

- Milestone: [Milestone 문서](../../../phase/operational-observability-provider-management/milestones/multi-provider-protocol-profile-native-messages.md)
- Phase: [PHASE.md](../../../../phase/operational-observability-provider-management/PHASE.md)

## 상태

[승인됨]

## SDD 잠금

- 상태: 해제
- 사용자 리뷰: 없음
- 잠금 항목: 없음

## 문제 / 비목표

- 문제: 현재 `openai_compat` adapter는 Chat/Responses operation path를 코드에서 조합하고 여러 vendor type을 같은 adapter type으로만 정규화하므로 Gemini의 `/v1beta/openai` 같은 base path, provider별 operation/auth/capability 차이를 안전하게 표현하기 어렵다. Anthropic Messages는 Edge에 없고 `agent-client/claude/iop-claude-gateway.py`가 Seulgi native passthrough와 OpenAI Chat 변환을 대신 소유해 Claude Code의 IOP 직접 호출 경로가 성립하지 않는다.
- 비목표:
  - `/v1/responses`를 모든 provider가 구현해야 하는 공통 표준으로 승격하거나 기존 Responses pipeline을 재설계하는 일
  - 사용자/provider credential 저장, slot alias, rotation과 secret delivery
  - vendor 간 자동 fallback, quota 최적화와 provider marketplace
  - provider별 beta/private 기능을 공통 request schema에 무손실로 강제 통합하는 일

## Source of Truth

| 영역 | 기준 | 메모 |
|------|------|------|
| Roadmap | [Milestone 문서](../../../phase/operational-observability-provider-management/milestones/multi-provider-protocol-profile-native-messages.md) | 목표, provider 범위와 완료 Task |
| Public API | `apps/edge/internal/openai`, [OpenAI-compatible API 계약](../../../../../agent-contract/outer/openai-compatible-api.md) | Chat/Responses 기존 표면과 새 Messages ingress host |
| Provider Runtime | `apps/edge/internal/service`, `apps/node/internal/adapters/openai_compat` | profile 선택, raw tunnel, URL/auth/model rewrite 실행 |
| Wire | `proto/iop/runtime.proto`, [Edge-Node Runtime Wire 계약](../../../../../agent-contract/inner/edge-node-runtime-wire.md) | operation-neutral provider tunnel request/frame |
| Config | `packages/go/config`, [Edge Config/Refresh 계약](../../../../../agent-contract/inner/edge-config-runtime-refresh.md) | protocol profile schema, alias normalization과 validation |
| Migration Evidence | `agent-client/claude/iop-claude-gateway.py`, `agent-client/pi/install.sh` | 현재 Messages bridge와 Responses 소비 동작의 참고 구현 |
| External Provider | [Claude API overview](https://platform.claude.com/docs/en/api/overview), [Claude Code LLM gateway 설정](https://docs.anthropic.com/en/docs/claude-code/llm-gateway), 각 provider 공식 API 문서와 live qualification record | profile별 operation/path/header/capability revision 기준 |
| User Decision | 2026-07-31 사용자 대화 | Chat Completions 공통 기준, Edge native Messages 흡수, Responses 현행 유지, 구현 시점 한정 live smoke |

## State Machine

| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| request/received | Chat, Messages, count-tokens, models 또는 기존 Responses 요청 수신 | request/authenticated / request/rejected | ingress method/path와 principal auth |
| request/authenticated | IOP principal 인증 성공 | request/route-resolved / request/rejected | request `model`, principal context와 model catalog |
| request/route-resolved | model route가 하나의 provider profile과 upstream model로 확정됨 | request/native-tunnel / request/bridge / request/rejected | profile protocol/capability snapshot |
| request/native-tunnel | ingress와 upstream protocol이 같고 operation이 지원됨 | response/streaming / response/terminal | raw body/header allowlist와 model rewrite |
| request/bridge | Anthropic Messages ingress가 `openai_chat` upstream을 선택함 | response/streaming / response/terminal / request/rejected | validated Messages↔Chat transform |
| request/rejected | path, model, capability, content block 또는 auth가 유효하지 않음 | response/terminal | public protocol별 typed error |
| response/streaming | upstream response start 뒤 body/event가 도착함 | response/streaming / response/terminal | protocol driver parser와 ordered emitter |
| response/terminal | complete, provider error, parse error, cancel 또는 timeout 확정 | 없음 | ingress protocol의 exactly-once terminal |

## Interface Contract

- 계약 원문: 기존 Chat/Responses는 [OpenAI-compatible API 계약](../../../../../agent-contract/outer/openai-compatible-api.md), provider tunnel은 [Edge-Node Runtime Wire 계약](../../../../../agent-contract/inner/edge-node-runtime-wire.md)을 따른다. 구현 시 native Messages 원문은 `agent-contract/outer/anthropic-compatible-api.md`에 새로 만들고 index에 등록한다.
- 입력:
  - public operation: `GET /v1/models`, `POST /v1/chat/completions`, `POST /v1/messages`, `POST /v1/messages/count_tokens`, 기존 `POST /v1/responses`다. `agent-client/claude` migration을 위해 `/anthropic/v1/messages`, `/anthropic/v1/messages/count_tokens`, `/anthropic/v1/models`를 같은 handler의 compatibility alias로 둘 수 있으나 canonical 계약은 `/v1/*`다.
  - protocol profile: stable profile id, driver(`openai_chat|anthropic_messages|openai_responses`), base URL, operation별 상대/절대 path, auth header/scheme declaration, supported operation/capability, upstream model mapping과 제한된 extension options를 가진다. `openai_responses`는 기존 선택적 driver이며 공통 qualification baseline이 아니다.
  - profile overlay: top-level reusable profile catalog를 `nodes[].providers[]` resource가 profile id로 참조한다. profile은 optional base profile 하나를 확장하고 endpoint/path/auth declaration/capability/extension만 override할 수 있으며, cycle/unknown base/conflicting driver는 config error다. runtime과 Node payload에는 상속 정보가 아니라 validation이 끝난 concrete profile만 전달한다.
  - Chat profile catalog: OpenAI, Gemini, Anthropic OpenAI-compatible, GLM, Kimi, MiniMax, MiMo, Grok와 Seulgi GPT를 `openai_chat` driver로 표현한다.
  - Messages profile catalog: Anthropic, MiniMax, MiMo와 Seulgi Claude처럼 native Messages가 검증된 endpoint만 `anthropic_messages` operation을 선언한다. 한 vendor가 두 protocol을 제공하면 별도 profile 또는 명시 capability로 표현하고 vendor 이름으로 protocol을 추정하지 않는다.
  - URL resolution: operation path가 절대 URL이면 그대로 사용하고 상대 path면 normalized base URL에 한 번만 결합한다. `/v1`, `/v1beta/openai`, `/api/paas/v4`, `/anthropic/v1` prefix는 문자열 suffix heuristic으로 추정하지 않고 profile operation path가 원문이다.
  - Messages auth: milestone 구현 시 기존 principal resolver를 추상화해 bearer와 `x-api-key`를 같은 IOP credential로 판정한다. Control Plane 원장 전환과 충돌 정책의 최종 소유자는 후속 credential-slot Milestone이다.
  - model discovery: 공식 Anthropic 요청에 필수인 `anthropic-version` header가 있는 `GET /v1/models`와 `/anthropic/v1/models`에는 Anthropic-compatible list/model shape를, header가 없는 일반 `/v1/models`에는 기존 OpenAI shape를 반환한다. 양쪽 모두 같은 internal model route snapshot을 사용하며 vendor/`User-Agent` 추정으로 schema를 선택하지 않는다.
  - Claude Code direct config: `ANTHROPIC_BASE_URL`은 Edge canonical root 또는 `/anthropic` compatibility mount를 가리키고, `ANTHROPIC_AUTH_TOKEN` 또는 `apiKeyHelper`가 반환한 IOP token을 principal auth에 사용한다. `Authorization`과 `X-Api-Key`가 함께 오면 같은 token이어야 한다.
  - Messages bridge input: top-level system, text/image content block, assistant content, tool definition, `tool_use`, `tool_result`, `tool_choice`, stop/max token, temperature와 profile capability가 선언한 thinking block을 Chat request로 변환한다. 변환 불가능한 block/option은 조용히 제거하지 않고 dispatch 전 `invalid_request_error`로 거부한다.
  - Anthropic version/beta headers: ingress는 지원하는 `anthropic-version`을 검증하고 알려진 `anthropic-beta` capability만 native upstream에 전달한다. Chat bridge가 의미를 구현하지 못하는 beta는 무시하지 않고 unsupported error로 거부한다.
  - count tokens: native upstream capability가 있으면 tunnel로 전달하고 Chat-only profile은 model profile에 등록된 deterministic counter/estimator를 사용한다. counter가 없으면 임의 provider 호출로 대체하지 않고 명시적인 unsupported error를 반환한다.
- 출력:
  - native Messages tunnel은 status, content type, Anthropic SSE event/body 순서와 provider error body를 public allowlist 안에서 보존한다.
  - Chat bridge는 Chat text/reasoning/tool call/finish reason/usage/error를 Anthropic message object 또는 `message_start` → content block events → `message_delta` → `message_stop` 순서로 변환한다.
  - usage는 input/output/cache/reasoning 중 양쪽 protocol에 대응되는 필드만 보존하고 알 수 없는 값을 만들어내지 않는다.
  - profile qualification record는 provider/profile/model, official-doc revision 또는 확인일, smoke date, result와 sanitized failure만 저장한다.
- Responses 보존:
  - `/v1/responses` route, `openai_responses` normalized/passthrough body/stream response와 `agent-client/pi` 소비는 현행 동작을 유지한다.
  - provider 공통 완료 판단은 Chat fixture와 profile이 선언한 Messages fixture를 기준으로 하며 Responses 지원이나 live smoke를 강제하지 않는다.
- 금지:
  - vendor 이름 분기만으로 endpoint/path/protocol을 선택하거나 모든 type을 의미 없는 단일 `openai_compat` capability로 취급하지 않는다.
  - native Messages response를 불필요하게 JSON decode/re-encode하거나 Chat bridge에서 알 수 없는 content/tool block을 silently drop하지 않는다.
  - `agent-client/claude` gateway를 Claude Code → IOP production 호출의 필수 hop으로 남기지 않는다.
  - live provider credential을 fixture, tracked config, CI secret requirement와 qualification artifact에 남기지 않는다.

## Acceptance Scenarios

| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `profile-contract` | valid/invalid driver, base overlay, operation, auth와 legacy provider type config가 있음 | config load와 runtime normalization을 수행함 | valid overlay는 immutable concrete profile이 되고 cycle/conflict/invalid 조합은 거부되며 legacy alias는 같은 profile id로 수렴한다. |
| S02 | `path-resolution` | `/v1`, `/v1beta/openai`, `/api/paas/v4`, `/anthropic/v1` base/path fixture가 있음 | operation URL을 resolve함 | prefix 중복/손실 없이 profile에 선언된 정확한 URL이 만들어진다. |
| S03 | `profile-catalog` | OpenAI, Gemini, Anthropic, GLM, Kimi, MiniMax, MiMo, Grok와 Seulgi Chat/Messages built-in/custom profile fixture가 있음 | 각 operation의 admission과 request build를 실행함 | 선언한 operation만 허용되고 header/model/path가 provider fixture와 일치한다. |
| S04 | `contract-sync` | protocol/config/wire 구현이 완료됨 | contract/spec drift check를 수행함 | public/inner contract와 spec index가 구현 source of truth를 정확히 가리킨다. |
| S05 | `messages-surface` | `ANTHROPIC_BASE_URL`, auth token/helper, `anthropic-version`, models, Messages와 count-tokens 요청이 있음 | canonical root와 `/anthropic` mount를 `agent-client` 없이 Edge에 직접 요청함 | 동일 principal 인증과 명시적 Anthropic model schema 선택 뒤 native 또는 bridge 실행으로 진입한다. |
| S06 | `messages-native` | native Messages non-stream/stream/error fixture가 있음 | raw tunnel로 요청함 | model/auth rewrite 외 body와 SSE ordering이 보존되고 terminal이 한 번만 발생한다. |
| S07 | `messages-chat-bridge` | text/image/tool/thinking/usage/error Chat fixtures가 있음 | Messages↔Chat bridge를 왕복함 | 지원 의미는 Anthropic 계약으로 복원되고 미지원 block은 dispatch 전에 거부된다. |
| S08 | `responses-regression` | 기존 Responses normalized/passthrough/provider-auth fixture가 있음 | 선택적 `openai_responses` profile/runtime test와 함께 실행함 | 기존 status/body/stream 의미와 Pi 소비 경로가 바뀌지 않는다. |
| S09 | `fixture-suite` | 외부 network와 credential이 없는 test 환경임 | 전체 provider contract suite를 실행함 | 모든 deterministic profile test가 구독 없이 반복 통과한다. |
| S10 | `live-smoke` | 구현 중인 profile의 opt-in credential과 대표 model 1개가 제공됨 | 한정 live smoke를 수동 실행함 | sanitized qualification record가 남고 일반 CI에는 live 의존성이 생기지 않는다. |

## Evidence Map

| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 |
|----------|-------------------|------------------|---------------------------|
| S01-S03 | config validation, URL resolver와 provider request golden tests | `agent-task/m-multi-provider-protocol-profile-native-messages/...` | `profile-contract`, `path-resolution`, `profile-catalog` Task id와 provider별 table result |
| S04 | contract/spec index check와 implementation path search | `agent-task/m-multi-provider-protocol-profile-native-messages/...` | `contract-sync` Task id와 drift 없음 근거 |
| S05-S07 | Claude Code request fixture, native tunnel bytes/SSE와 Messages↔Chat golden tests | `agent-task/m-multi-provider-protocol-profile-native-messages/...` | `messages-surface`, `messages-native`, `messages-chat-bridge` Task id별 direct Edge evidence |
| S08 | 기존 Responses route/stream/provider-auth regression suite | `agent-task/m-multi-provider-protocol-profile-native-messages/...` | `responses-regression` Task id와 기존 test 통과 근거 |
| S09 | credential-free full provider fixture suite | `agent-task/m-multi-provider-protocol-profile-native-messages/...` | `fixture-suite` Task id와 network-disabled test 결과 |
| S10 | opt-in one-shot command와 sanitized qualification record | `agent-task/m-multi-provider-protocol-profile-native-messages/...` | `live-smoke` Task id, provider/profile/model/date/result; secret 없음 |

## Cross-repo Dependencies

- 없음

## Drift Check

- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- [x] Evidence Map이 code-review/complete.log에서 검증 가능하다.
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- [x] 사용자 리뷰가 필요한 항목은 `USER_REVIEW.md`에만 남겼다.

## 사용자 리뷰 이력

- 2026-07-31: 사용자가 Chat Completions 공통 기준, Edge의 Anthropic Messages 직접 흡수, Responses 현행 유지와 구현 시점 한정 smoke를 확정했다.

## 작업 컨텍스트

- 표준선: protocol driver는 wire/stream semantics를, provider profile은 endpoint/path/auth/model/capability 차이를 소유한다. 공통 profile로 표현할 수 없는 검증된 차이만 extension hook으로 추가한다.
- 후속 SDD: [SDD.md](../../../../sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md)
