# Milestone: OpenAI-compatible Tool Call Boundary Hardening

## 위치

- Roadmap: `agent-roadmap/ROADMAP.md`
- Phase: `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`

## 목표

OpenAI-compatible `tools[]`가 있는 요청에서 provider-pool 또는 provider route가 raw text `<tool_call>` block을 반환하더라도, Edge가 이를 요청 tool schema와 대조해 구조화된 `tool_calls` 또는 명확한 validation error/retry로 처리한다.
존재하지 않는 tool name, schema 불일치, chat-template sentinel token이 Pi/Cline 같은 클라이언트 화면으로 그대로 노출되지 않게 한다.

## 상태

[완료]

## 승격 조건

- 없음

## 구현 잠금

- 상태: 해제
- SDD: 불필요
- SDD 문서: 없음
- SDD 사유: 기존 OpenAI-compatible `tools[]`, `tool_calls`, text-tool fallback, validation retry 계약을 강화하는 호환성 보정이며 새 공개 request/response field를 만들지 않는다.
- 잠금 해제 조건: 없음
- 결정 필요: 없음

## 범위

- `/v1/chat/completions` provider-pool/provider route의 stream/non-stream tool-call 경계 검증
- 요청 `tools[]` schema가 있는 경우 raw `<tool_call>` text block을 기존 text tool-call parser로 대조하고, schema-valid block은 OpenAI-compatible `tool_calls`로 정규화
- 요청 tool 목록에 없는 function name, schema 불일치, malformed arguments를 성공 content로 flush하지 않고 validation error 또는 bounded retry로 처리
- `<|mask_end|>` 같은 chat-template sentinel token이 최종 assistant content/reasoning에 남지 않도록 출력 정규화
- Pi/Cline처럼 OpenAI-compatible tools를 사용하는 클라이언트에서 raw tool-call leak이 재현되지 않는 dev smoke와 회귀 테스트

## 기능

### Epic: [tool-boundary] Tool Call Boundary Guard

OpenAI-compatible provider 응답의 tool-call 후보를 요청 schema 기준으로 구조화하거나 차단하는 capability를 묶는다.

- [x] [provider-raw-parse] provider-pool/provider route에서도 요청 `tools[]`가 있고 assistant content에 raw `<tool_call>` block이 있으면 기존 text tool-call parser를 적용한다. 검증: `bash` raw block은 `message.tool_calls` 또는 stream `delta.tool_calls`로 정규화되고 raw block이 content에 남지 않는다.
- [x] [unknown-tool-error] raw tool-call block의 function name이 요청 tool 목록에 없으면 일반 assistant text로 flush하지 않고 validation error 또는 bounded retry로 처리한다. 검증: `TOOL_CALLS.push_changes` 형태의 unknown tool output이 Pi/Cline 화면으로 노출되지 않는다.
- [x] [stream-flush-guard] streaming 경로에서 tool-call 후보를 보류한 뒤 terminal event에서 구조화/차단하며, 실패한 raw block을 마지막 flush로 내보내지 않는다. 검증: live SSE와 buffered SSE 테스트가 모두 raw `<tool_call>` leak 없이 `tool_calls` 또는 error로 종료된다.
- [x] [sentinel-sanitize] provider output normalization이 `<|mask_end|>` 같은 알려진 chat-template sentinel token을 assistant content/reasoning 최종 출력에서 제거한다. 검증: sentinel token이 tool-call 뒤나 단독 content로 들어와도 OpenAI-compatible 응답에 남지 않는다.
- [x] [native-preserve] provider가 이미 native OpenAI-compatible `tool_calls`를 반환하는 경로는 기존 pass-through와 schema-normalization 동작을 유지한다. 검증: native `tool_calls` server tests와 forced/auto direct tool-call smoke가 회귀 없이 통과한다.
- [x] [client-smoke] Pi/Cline형 긴 agent prompt와 `tools[]` 요청을 직접 Edge에 호출해 unknown tool hallucination이 raw text로 노출되지 않는 근거를 남긴다. 검증: 재현 요청, response finish reason/error type, Edge trace를 `agent-test/dev`에 기록한다.
- [x] [contract-docs] OpenAI-compatible 계약 문서와 dev 운영 문서가 provider raw tool-call 후보의 정규화/차단, unknown tool 처리, sentinel sanitize 정책을 설명한다.

## 완료 리뷰

- 상태: 통과
- 요청일: 2026-07-05
- 완료 근거: `01_provider_text_boundary/complete.log`, `02+01_contract_dev_smoke/complete.log`, `03+02_closure_verification/complete.log`가 모든 기능 Task id의 PASS, Go 검증, dev-runtime non-stream/stream smoke evidence를 기록한다.
- 완료 근거: 종료 전 코드레벨 재검토에서 native stream `tool_calls`와 raw/partial text tool-call 후보가 함께 오는 경로의 `FlushNonCandidate()` guard와 회귀 테스트를 확인했다.
- 완료 근거: 현재 checkout 기준 `go test ./apps/edge/internal/openai`, `go test ./apps/edge/...`, `go test ./...`가 통과했다.
- 검토 항목:
  - [x] valid raw text tool call은 구조화되고 raw block이 노출되지 않는다
  - [x] unknown/malformed tool call은 성공 content로 노출되지 않는다
  - [x] Pi/Cline형 OpenAI-compatible tools smoke 근거가 남아 있다
- agent-ui 상태 반영: 해당 없음
- 리뷰 코멘트: 남은 차단 항목 없음. archive 전 workspace lock 조회 결과 현재 Milestone 관련 lock은 없다.

## 범위 제외

- `tools[]`가 없는 요청에서 임의 자연어를 tool call로 추론하는 모델-assisted gate
- Pi 자체 provider/model 설정 파일의 소유권 이전 또는 자동 생성
- CLI `adapter=cli,target=pi` profile 구현과 Pi JSON stream emitter
- 요청 실행 로그/usage ledger의 장기 저장 schema 구현
- provider별 chat template 전체 교체나 모델별 fine-tune/prompt policy 변경

## 작업 컨텍스트

- 관련 경로: `apps/edge/internal/openai`, `apps/node/internal/adapters/openai_compat`, `agent-contract/outer/openai-compatible-api.md`, `docs/openai-compatible-api-contract.md`, `agent-test/dev`
- 표준선(선택): 명시적 `tools[]`가 있는 요청은 runtime-only validation을 우선한다. 요청 schema에 없는 tool name은 client-visible 성공 content가 아니라 validation failure로 다룬다.
- 표준선(선택): provider-pool은 native `tool_calls`만 신뢰하지 않고, raw text tool-call 후보가 보이면 같은 OpenAI-compatible boundary 정책으로 검증한다.
- 완료 메모(2026-07-04): provider route 또는 provider-pool에서 assistant content에 raw `<tool_call>` text가 들어오면 요청 `tools[]` schema 기준으로 구조화하거나 차단한다. 관련 단위 테스트는 provider raw text synthesis, malformed/unknown blocking, multi-candidate validation을 포함한다.
- 완료 메모(2026-07-04): unknown text tool-call은 client-visible success content로 남기지 않고 bounded retry 또는 `tool_validation_error`로 처리한다. live SSE와 strict buffered stream 경로도 raw block을 flush하지 않는다.
- 완료 메모(2026-07-04): [sentinel-sanitize]는 `apps/edge/internal/openai/strict_output.go`, `stream.go`, `server_test.go`에서 non-stream Chat Completions, live SSE, Responses 출력의 `<|mask_end|>` 제거와 tool-call 뒤 sentinel 제거 회귀 테스트를 추가해 완료했다. 검증: `go test ./apps/edge/internal/openai`, `go test ./apps/edge/...`.
- 완료 메모(2026-07-05): [stream-flush-guard]와 [native-preserve]는 native `tool_calls` 완료 시 raw/partial text tool-call 후보를 flush하지 않는 `FlushNonCandidate()` guard와 회귀 테스트로 보강했다. 검증: `go test ./apps/edge/internal/openai`, `go test ./apps/edge/...`, `go test ./...`.
- 완료 메모(2026-07-05): [client-smoke]는 final-source dev-runtime rebuild/restart 뒤 Pi/Cline형 non-stream/stream OpenAI-compatible smoke evidence를 `agent-test/runs/boundary-closure-partialguard-20260705_044102`에 남긴 것으로 종료했다.
- 선행 작업: Tool Call Runtime 검증 재시도 MVP, OpenAI-compatible Think 제어 MVP에서 확인된 provider-pool/Pi raw leak 진단
- 후속 작업: Tool Call 판정 모델 Gate 리뷰, 요청 실행 로그와 Usage Ledger 기반
- 확인 필요: 없음
