Repository navigation
[Feat] fetch 기반 run SSE reader와 run API client·요청 검증 [1.89] - #310
Merged
Merged
Conversation
9 tasks done
Member
Author
리뷰 1회차(r189) 반영 —
|
Member
Author
|
리뷰 권고 #1·#7 반영 (사용자 승인 (b)): commit 4cf6541
CI: Frontend CI run 39353456941 통과 (test·typecheck:test·build). |
- sse_reader: CR/LF/CRLF·청크 경계 처리, heartbeat comment 무시, 종료 사유(terminal·interrupted·aborted·rejected) 구분 - run_api_client: POST/GET SSE, 상태·취소·대화 조회, stream 전 401 1회 갱신 - run_api_error: ApiResponse 오류를 status·code·activeRunId·Retry-After로 해석 - run_request: requestId 생성, Python str.strip() 집합·code point 길이 검증 - client.ts: interceptor와 대기큐를 공유하는 refreshAccessToken export Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts 입력 검사, body 읽기 실패 구분 - 401 갱신을 AbortSignal과 경합시켜 abort를 바로 반영하고 재요청하지 않음 - frame seq는 1..2^53-1 정수, runId는 소문자 UUID가 아니면 protocol 오류 - streamRunEvents: runId 소문자 UUID·cursor가 같은 run인지 확인, SSE 함수는 TypeError를 reject로 통일 - 오류·JSON body 읽기 중 abort·네트워크 실패를 계약 밖 오류로 삼키지 않음 - noteId·turnAttachmentIds 값이 undefined면 키가 없는 것으로 판정 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- interceptor 401 분기의 갱신 본문 복제를 없애고 refreshAccessToken()을 기다린 뒤 재요청한다. - refresh token이 없을 때 isRefreshing이 true로 남아 이후 갱신이 끝나지 않던 기존 결함을 고친다. - 갱신 실패 시 /error/401 redirect, refresh token 없음 시 원래 401 reject 등 기존 동작을 유지하고 테스트로 확인한다. - 다른 경로(fetch)가 시작한 갱신이 실패해도 기다리던 axios 요청이 redirect한다(#6 간접 차이 해소). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chowon442
force-pushed
the
worktree-task-1.89
branch
from
October 20, 2026 01:30
4cf6541 to
8605de4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📌 관련 이슈
🏷️ PR 타입
📝 작업 내용
browser-run-v1(pin
browser-run-fixtures-v1) 경로를 한 번 연결·한 번 요청하는 통신 계층을src/features/run/api/에 추가했다. 재접속·backoff·409 복원(1.93), 상태 판단(1.90), 화면은 하지 않았다. 기존parse_sse_stream.ts·editor_api.ts는 그대로 뒀다(1.95).sse_reader.ts— fetch body를 읽는 SSE reader. CR·LF·CRLF와 청크 경계(줄·CRLF·UTF-8 다바이트가 갈린 경우)를 처리한다.: heartbeatcomment와 data 없는 블록은 frame·cursor가 아니다. 빈 줄 없이 잘린 마지막 블록은 버린다.event=data.type,id={runId}:{seq}, 같은 run인지 확인하고, 어긋나면 protocol 오류로 연결을 끊는다(조용히 버리지 않음).run_api_error.ts— stream 전 JSONApiResponse를 HTTP status·code로 해석한다. RUN4091은activeRunId, RUN4291은Retry-After(delta-seconds·HTTP-date → 초)를 담는다. 계약 밖 응답(COMMON500, proxy HTML 등)은code: null이다.run_api_client.ts— 아래 5개 경로.Authorization: Bearer를 붙이고, 401이면 token을 한 번 갱신한 뒤 같은 요청(같은 body)을 다시 보낸다.run_request.ts—requestId는 소문자 UUID다(crypto.randomUUID, 비보안 context에서는getRandomValues로 대체). Pythonstr.strip()집합을 직접 구현했고 code point로 1~10,000을 센다(JS.length·trim()사용 안 함). closed key·ID 패턴·selection·video=RUN4002도 검사한다. noteId가 없으면 키를 뺀다.src/shared/api/client.ts—refreshAccessToken()export를 추가만 했다(기존 interceptor 동작은 그대로). ServerAuthService.refreshToken은 refresh token을 쓰면 바로 폐기하고 새로 발급한다(1회용 회전). fetch 쪽이 따로 갱신하면 axios interceptor와 동시에 갱신할 때 한쪽이 실패해 로그아웃된다. 그래서 interceptor의isRefreshing·대기큐를 같이 쓴다.공개 인터페이스 (
@/features/run/api)검증
현재 head
8605de4(origin/dev509028a위로 rebase) 기준이다.vitest run src/features/run/api src/shared/api(이 PR 파일) → 5 files, 217 tests passedstreams/valid10개: SSE 바이트 →frames와 같은 결과. 1바이트·임의 분할, 두 조각 분할(모든 위치, LF·CRLF), CRLF·CR 줄 끝heartbeat-invalid3종(id 붙은 heartbeat는 cursor를 바꾸지 않고, data·event 붙은 것은 protocol 오류)requests/valid10개 통과,requests/invalid43개 기대 code(RUN4001·RUN4002)와 일치. strip 집합 29자, U+FEFF(JS trim과 다름), astral 10,000/10,001, text·latex 앞뒤 공백을 붙인 상한responses/valid오류 13종 status·code·message. RUN4091 activeRunId, RUN4291 Retry-After. 계약 밖 응답cursors/cases.json의 schema 거부 판정과isEventId가 일치refreshAccessToken동시 호출 → refresh 요청 1회streamRunEventsrunId·cursor 검사/error/401redirect, refresh token 없음 시 원래 401 reject·갱신 상태 고착 없음,skipErrorRedirect, fetch 쪽 갱신 실패 시 대기 axios 요청 redirect, 재요청 401 재갱신 없음eslint(변경 파일),tsc -p tsconfig.app.json,tsc -p tsconfig.test.json통과pnpm run build통과✅ 체크리스트
공통
pnpm tsc --noEmit타입 체크를 통과했습니다type/이슈번호-작업명) — 오케스트레이터가 지정한worktree-task-1.89를 사용인증 관련 변경 시
client.ts는 export만 추가했고 interceptor·login 코드는 바꾸지 않았다(실제 로그인으로는 확인하지 않음)📎 기타 참고사항
RunStreamEnd.lastEventId이고 heartbeat는 반영되지 않는다.interrupted는 실행 취소가 아니다(backoff 뒤streamRunEvents).rejected+ RUN4041이면 멈춘다.rejected+ RUN4091이면error.detail.activeRunId로streamRunEvents. RUN4291은retryAfterSeconds.protocol원인은 같은 seq에서 반복될 수 있으니 재접속 정책에서 구분한다.viewerFocus.page: schema에는 상한이 없지만 Server가 Java int로 받으므로 검증에서 1..2^31-1로 제한했다(1.75 메모: 판정이 갈리는 값은 보내지 않음).client.tsinterceptor가 refresh token이 없을 때isRefreshing = true를 걸어 둔 채 return하던 결함은, interceptor가refreshAccessToken()을 쓰도록 합치면서 고쳤다.test_streams.ts는src/features/run/api/에 뒀다(1.88 소유src/test/contracts/는 건드리지 않음).🤖 Generated with Claude Code