diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md
index fff53edf..91eacaaa 100644
--- a/backend/CLAUDE.md
+++ b/backend/CLAUDE.md
@@ -440,6 +440,12 @@ docker compose up -d
IP 로 해석되는 공개 도메인도 막힌다). 거부 응답에 내부 주소는 노출하지 않는다. DNS 해석기는 주입
가능(테스트가 네트워크 미의존). 여긴 첫 관문이고 실질 방어선은 AI 쪽 `url_guard.py` 다.
- 같은 URL 재등록은 409(`RESUME_URL_DUPLICATE`) — 임베딩 중복으로 질문이 쏠리는 것 방지.
+- **같은 설정으로 다시 면접 본 구현**: `POST /api/sessions/{id}/retry` → `SessionService.retry` 가
+ 원본 세션의 설정(모드·직군·질문 수·JD 등)을 복사해 `create` 를 호출한다. **연결 자료는 지금도
+ 살아있고 ANALYZED 인 것만** 다시 잇는다 — `linkContexts` 는 삭제된 문서에 `DOC_NOT_FOUND`(404),
+ 분석 미완료에 `DOC_NOT_ANALYZED` 를 던지므로, 원본 설정을 그대로 재전송하면 그 사이 자료 하나
+ 지운 사용자는 재도전 자체가 막힌다. 빠진 자료는 응답 `contextDocumentIds` 를 원본과 비교해
+ 프론트가 안내한다.
- **Spring AI 미사용** — LLM·임베딩 호출은 모두 AI 서버 위임. Core는 RabbitMQ 발행만 담당.
- **Redis 미사용** — 휘발성 데이터는 DB short-lived 레코드 또는 인메모리로.
diff --git a/backend/openapi.json b/backend/openapi.json
index 1a25bc37..fcb66c30 100644
--- a/backend/openapi.json
+++ b/backend/openapi.json
@@ -371,6 +371,55 @@
}
}
},
+ "/api/sessions/{sessionId}/retry" : {
+ "post" : {
+ "tags" : [ "Sessions" ],
+ "summary" : "같은 설정으로 다시 면접 (US-13 재도전)",
+ "description" : "기존 세션의 설정(모드·직군·질문 수·JD 등)을 그대로 복사해 새 세션을 만든다. 연결 자료는 지금도 살아있고 분석이 끝난 것만 다시 잇는다 — 그 사이 삭제된 자료 때문에 재도전 전체가 404 로 막히지 않게 하기 위함. 응답의 contextDocumentIds 를 원본과 비교하면 무엇이 빠졌는지 알 수 있다.",
+ "operationId" : "retrySession",
+ "parameters" : [ {
+ "name" : "sessionId",
+ "in" : "path",
+ "required" : true,
+ "schema" : {
+ "type" : "integer",
+ "format" : "int64"
+ }
+ } ],
+ "responses" : {
+ "201" : {
+ "description" : "새 세션 생성 완료",
+ "content" : {
+ "*/*" : {
+ "schema" : {
+ "$ref" : "#/components/schemas/SessionResponse"
+ }
+ }
+ }
+ },
+ "401" : {
+ "description" : "인증 실패",
+ "content" : {
+ "*/*" : {
+ "schema" : {
+ "$ref" : "#/components/schemas/SessionResponse"
+ }
+ }
+ }
+ },
+ "404" : {
+ "description" : "원본 세션 없음",
+ "content" : {
+ "*/*" : {
+ "schema" : {
+ "$ref" : "#/components/schemas/SessionResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/api/sessions/{sessionId}/messages" : {
"get" : {
"tags" : [ "Session Messages" ],
diff --git a/backend/src/main/java/com/stackup/stackup/session/application/SessionService.java b/backend/src/main/java/com/stackup/stackup/session/application/SessionService.java
index 612eccfd..0c44572e 100644
--- a/backend/src/main/java/com/stackup/stackup/session/application/SessionService.java
+++ b/backend/src/main/java/com/stackup/stackup/session/application/SessionService.java
@@ -92,6 +92,43 @@ public SessionResult create(Long userId, SessionCreateCommand command) {
return SessionResult.of(session, linkedIds);
}
+ /**
+ * 같은 설정으로 새 면접을 만든다(US-13 재도전).
+ *
+ *
세션 설정을 그대로 복사하되 **자료(context)는 지금도 살아있는 것만** 잇는다. 원본 설정을
+ * 그대로 재전송하면 그 사이 삭제된 이력서 하나 때문에 {@code DOC_NOT_FOUND}(404)로 전체가
+ * 막힌다 — 사용자 입장에선 "다시 하기"가 이유 없이 실패하는 것으로 보인다. 빠진 자료는
+ * 응답의 {@code contextDocumentIds} 로 드러나므로 호출자가 원본과 비교해 안내할 수 있다.
+ */
+ @Transactional
+ public SessionResult retry(Long userId, Long sourceSessionId) {
+ InterviewSession source = loadOwned(userId, sourceSessionId);
+ List reusableDocumentIds = contextDocumentIds(sourceSessionId).stream()
+ .filter(id -> isReusableContext(id, userId))
+ .toList();
+
+ return create(userId, new SessionCreateCommand(
+ source.getTitle(),
+ source.getMemo(),
+ source.getMode(),
+ new ArrayList<>(source.getJobCategories()),
+ source.getMaxQuestions(),
+ source.getMaxDurationMinutes(),
+ source.getGeneralQuestionCount(),
+ source.getMaxFollowupsPerQuestion(),
+ reusableDocumentIds,
+ source.getTargetCompanyName(),
+ source.getTargetJobDescription()
+ ));
+ }
+
+ // 삭제됐거나 아직 분석이 끝나지 않은 자료는 조용히 제외한다(linkContexts 는 둘 다 예외를 던진다).
+ private boolean isReusableContext(Long documentId, Long userId) {
+ return documentRepository.findActiveByIdAndOwner(documentId, userId)
+ .filter(doc -> doc.getAnalysisStatus() == AnalysisStatus.ANALYZED)
+ .isPresent();
+ }
+
public Page listPaged(Long userId, Pageable pageable) {
loadUser(userId);
return sessionRepository.findByUser_IdAndDeletedFalse(userId, pageable)
diff --git a/backend/src/main/java/com/stackup/stackup/session/presentation/SessionController.java b/backend/src/main/java/com/stackup/stackup/session/presentation/SessionController.java
index 03f1f894..3e72762f 100644
--- a/backend/src/main/java/com/stackup/stackup/session/presentation/SessionController.java
+++ b/backend/src/main/java/com/stackup/stackup/session/presentation/SessionController.java
@@ -87,6 +87,28 @@ public SessionResponse get(
return SessionResponse.from(sessionService.get(principal.userId(), sessionId));
}
+ @Operation(
+ operationId = "retrySession",
+ summary = "같은 설정으로 다시 면접 (US-13 재도전)",
+ description = "기존 세션의 설정(모드·직군·질문 수·JD 등)을 그대로 복사해 새 세션을 만든다. "
+ + "연결 자료는 지금도 살아있고 분석이 끝난 것만 다시 잇는다 — 그 사이 삭제된 자료 때문에 "
+ + "재도전 전체가 404 로 막히지 않게 하기 위함. 응답의 contextDocumentIds 를 원본과 비교하면 "
+ + "무엇이 빠졌는지 알 수 있다."
+ )
+ @ApiResponses({
+ @ApiResponse(responseCode = "201", description = "새 세션 생성 완료"),
+ @ApiResponse(responseCode = "401", description = "인증 실패"),
+ @ApiResponse(responseCode = "404", description = "원본 세션 없음")
+ })
+ @PostMapping("/{sessionId}/retry")
+ @ResponseStatus(HttpStatus.CREATED)
+ public SessionResponse retry(
+ @AuthenticationPrincipal UserPrincipal principal,
+ @PathVariable Long sessionId
+ ) {
+ return SessionResponse.from(sessionService.retry(principal.userId(), sessionId));
+ }
+
@Operation(operationId = "updateSessionMeta", summary = "세션 제목/메모 수정")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "갱신 완료"),
diff --git a/backend/src/test/java/com/stackup/stackup/session/application/SessionServiceTest.java b/backend/src/test/java/com/stackup/stackup/session/application/SessionServiceTest.java
index 42dc1827..7ec5ad74 100644
--- a/backend/src/test/java/com/stackup/stackup/session/application/SessionServiceTest.java
+++ b/backend/src/test/java/com/stackup/stackup/session/application/SessionServiceTest.java
@@ -176,6 +176,87 @@ void create_jobTailoredStoresCompanyAndJd() {
assertThat(result.title()).isEqualTo("토스 백엔드 직무 맞춤 면접");
}
+ // ── retry (같은 설정으로 다시) ─────────────────────────────────────────────
+
+ @Test
+ void retry_copiesSettingsIntoNewSession() {
+ User user = userFixture(1L);
+ InterviewSession source = InterviewSession.create(
+ user, "백엔드 모의면접", "메모", SessionMode.JOB_TAILORED,
+ List.of(JobCategory.BACKEND, JobCategory.INFRA), 8, 45, 4, 3
+ );
+ ReflectionTestUtils.setField(source, "id", 50L);
+ source.assignTargetRole("스택업", "JD 본문");
+
+ when(sessionRepository.findByIdAndUser_IdAndDeletedFalse(50L, 1L)).thenReturn(Optional.of(source));
+ when(contextRepository.findBySession_Id(50L)).thenReturn(List.of());
+ when(userRepository.findByIdAndDeletedFalse(1L)).thenReturn(Optional.of(user));
+ when(sessionRepository.save(any(InterviewSession.class))).thenAnswer(inv -> {
+ InterviewSession s = inv.getArgument(0);
+ ReflectionTestUtils.setField(s, "id", 101L);
+ return s;
+ });
+
+ SessionResult result = service.retry(1L, 50L);
+
+ assertThat(result.id()).isEqualTo(101L);
+ assertThat(result.title()).isEqualTo("백엔드 모의면접");
+ assertThat(result.memo()).isEqualTo("메모");
+ assertThat(result.mode()).isEqualTo(SessionMode.JOB_TAILORED);
+ assertThat(result.jobCategories())
+ .containsExactly(JobCategory.BACKEND, JobCategory.INFRA);
+ assertThat(result.maxQuestions()).isEqualTo(8);
+ assertThat(result.maxDurationMinutes()).isEqualTo(45);
+ assertThat(result.generalQuestionCount()).isEqualTo(4);
+ assertThat(result.maxFollowupsPerQuestion()).isEqualTo(3);
+ // JOB_TAILORED 의 JD 가 빠지면 새 세션이 SESSION_JD_REQUIRED 로 막힌다.
+ assertThat(result.targetCompanyName()).isEqualTo("스택업");
+ assertThat(result.targetJobDescription()).isEqualTo("JD 본문");
+ assertThat(result.status()).isEqualTo(SessionStatus.READY);
+ verify(events).publishEvent(any(SessionCreatedEvent.class));
+ }
+
+ // 원본 설정을 그대로 재전송하면 그 사이 삭제된 자료 하나 때문에 404 로 전체가 막힌다.
+ @Test
+ void retry_skipsDeletedOrUnanalyzedContextDocuments() {
+ User user = userFixture(1L);
+ InterviewSession source = sessionFixture(50L);
+ // mock 생성·스터빙은 when(...) 바깥에서 먼저 끝낸다(중첩 스터빙 금지).
+ AnalyzedDocument alive = analyzedDocFixture(7L, AnalysisStatus.ANALYZED);
+ AnalyzedDocument deleted = analyzedDocFixture(8L, AnalysisStatus.ANALYZED);
+ AnalyzedDocument reanalyzing = analyzedDocFixture(9L, AnalysisStatus.PROCESSING);
+ List contexts = List.of(
+ contextFixture(source, alive),
+ contextFixture(source, deleted), // 그 사이 삭제됨
+ contextFixture(source, reanalyzing) // 재분석 중
+ );
+
+ when(sessionRepository.findByIdAndUser_IdAndDeletedFalse(50L, 1L)).thenReturn(Optional.of(source));
+ when(contextRepository.findBySession_Id(50L)).thenReturn(contexts);
+ when(documentRepository.findActiveByIdAndOwner(7L, 1L)).thenReturn(Optional.of(alive));
+ when(documentRepository.findActiveByIdAndOwner(8L, 1L)).thenReturn(Optional.empty());
+ when(documentRepository.findActiveByIdAndOwner(9L, 1L)).thenReturn(Optional.of(reanalyzing));
+ when(userRepository.findByIdAndDeletedFalse(1L)).thenReturn(Optional.of(user));
+ when(sessionRepository.save(any(InterviewSession.class))).thenAnswer(inv -> {
+ InterviewSession s = inv.getArgument(0);
+ ReflectionTestUtils.setField(s, "id", 101L);
+ return s;
+ });
+
+ SessionResult result = service.retry(1L, 50L);
+
+ // 살아있는 7L 만 다시 연결 — 나머지는 조용히 제외한다(호출자가 원본과 비교해 안내).
+ assertThat(result.contextDocumentIds()).containsExactly(7L);
+ }
+
+ @Test
+ void retry_rejectsSessionOfAnotherUser() {
+ when(sessionRepository.findByIdAndUser_IdAndDeletedFalse(50L, 1L)).thenReturn(Optional.empty());
+
+ assertThatThrownBy(() -> service.retry(1L, 50L))
+ .isInstanceOf(DomainException.class);
+ }
+
@Test
void start_transitionsReadyToInProgress() {
InterviewSession session = sessionFixture(50L);
@@ -290,6 +371,10 @@ private InterviewSession sessionFixture(Long id) {
return s;
}
+ private SessionContext contextFixture(InterviewSession session, AnalyzedDocument doc) {
+ return SessionContext.link(session, doc);
+ }
+
private AnalyzedDocument analyzedDocFixture(Long id, AnalysisStatus status) {
AnalyzedDocument doc = mock(AnalyzedDocument.class);
org.mockito.Mockito.lenient().when(doc.getId()).thenReturn(id);
diff --git a/frontend/src/features/interview/api/sessionApi.ts b/frontend/src/features/interview/api/sessionApi.ts
index 2d405e29..f818c190 100644
--- a/frontend/src/features/interview/api/sessionApi.ts
+++ b/frontend/src/features/interview/api/sessionApi.ts
@@ -11,6 +11,11 @@ export async function getSession(id: number): Promise {
return (await apiClient.get(`/api/sessions/${id}`)).data
}
+// 같은 설정으로 새 세션 생성. 삭제된 자료는 서버가 알아서 빼고 잇는다(응답의 contextDocumentIds).
+export async function retrySession(id: number): Promise {
+ return (await apiClient.post(`/api/sessions/${id}/retry`)).data
+}
+
export async function startSession(id: number): Promise {
return (await apiClient.patch(`/api/sessions/${id}/start`)).data
}
diff --git a/frontend/src/features/interview/index.ts b/frontend/src/features/interview/index.ts
index 37eab52b..be884065 100644
--- a/frontend/src/features/interview/index.ts
+++ b/frontend/src/features/interview/index.ts
@@ -3,4 +3,6 @@ export { InterviewTranscript } from './ui/InterviewTranscript'
export { InterviewSetupForm } from './ui/setup/InterviewSetupForm'
export type { DocOption } from './ui/setup/ContextDocumentPicker'
export { useCreateSession } from './model/useCreateSession'
+export { useRetrySession } from './model/useRetrySession'
+export { useSession } from './model/useSession'
export { useLiveInterview } from './model/useLiveInterview'
diff --git a/frontend/src/features/interview/model/useRetrySession.ts b/frontend/src/features/interview/model/useRetrySession.ts
new file mode 100644
index 00000000..dde8c9b7
--- /dev/null
+++ b/frontend/src/features/interview/model/useRetrySession.ts
@@ -0,0 +1,33 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query'
+import { useNavigate } from 'react-router-dom'
+import { toast } from '@/shared/ui'
+import { retrySession } from '../api/sessionApi'
+import { sessionKeys } from './useSession'
+
+/**
+ * 같은 설정으로 다시 면접. 서버가 설정을 복사하고, 그 사이 삭제·재분석 중인 자료는 빼고 잇는다.
+ *
+ * @param sourceContextCount 원본 세션이 연결하고 있던 자료 수. 새 세션에 붙은 수와 다르면
+ * 조용히 빠진 것이므로 사용자에게 알린다 — 모르고 시작하면 질문 근거가 달라진 걸 알 수 없다.
+ */
+export function useRetrySession(sourceContextCount?: number) {
+ const navigate = useNavigate()
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: retrySession,
+ onSuccess: (session) => {
+ void queryClient.invalidateQueries({ queryKey: sessionKeys.all })
+ const linked = session.contextDocumentIds?.length ?? 0
+ if (sourceContextCount != null && linked < sourceContextCount) {
+ toast.info(
+ `삭제된 자료 ${sourceContextCount - linked}개는 제외하고 시작합니다.`,
+ )
+ }
+ navigate(`/sessions/${session.id}`)
+ },
+ onError: () => {
+ toast.error('면접을 다시 만들지 못했어요. 잠시 후 다시 시도해 주세요.')
+ },
+ })
+}
diff --git a/frontend/src/features/interview/ui/live/LiveInterview.tsx b/frontend/src/features/interview/ui/live/LiveInterview.tsx
index c355a842..8da8d818 100644
--- a/frontend/src/features/interview/ui/live/LiveInterview.tsx
+++ b/frontend/src/features/interview/ui/live/LiveInterview.tsx
@@ -59,7 +59,13 @@ export function LiveInterview({ sessionId }: { sessionId: number }) {
return
}
if (status !== 'IN_PROGRESS') {
- return
+ return (
+
+ )
}
// 면접은 시작됐지만 첫 질문이 아직 안 왔으면 스테이지 진입 전 대기 화면을 보여준다.
if (!firstQuestionReady) {
diff --git a/frontend/src/features/interview/ui/live/SessionEndedPanel.test.tsx b/frontend/src/features/interview/ui/live/SessionEndedPanel.test.tsx
new file mode 100644
index 00000000..ee7d01df
--- /dev/null
+++ b/frontend/src/features/interview/ui/live/SessionEndedPanel.test.tsx
@@ -0,0 +1,80 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { MemoryRouter } from 'react-router-dom'
+import { SessionEndedPanel } from './SessionEndedPanel'
+
+const retryMutate = vi.fn()
+const lastSourceCount = vi.fn()
+vi.mock('../../model/useRetrySession', () => ({
+ useRetrySession: (count?: number) => {
+ lastSourceCount(count)
+ return { mutate: retryMutate, isPending: false }
+ },
+}))
+vi.mock('../InterviewTranscript', () => ({
+ InterviewTranscript: () => ,
+}))
+
+beforeEach(() => {
+ retryMutate.mockClear()
+ lastSourceCount.mockClear()
+})
+
+function renderPanel(status: 'COMPLETED' | 'INTERRUPTED' | 'CANCELLED') {
+ return render(
+
+
+ ,
+ )
+}
+
+describe('SessionEndedPanel', () => {
+ it('완료 세션은 피드백 링크와 기록을 함께 보여준다', () => {
+ renderPanel('COMPLETED')
+
+ expect(screen.getByRole('link', { name: '피드백 보기' })).toHaveAttribute(
+ 'href',
+ '/sessions/7/feedback',
+ )
+ expect(screen.getByTestId('transcript')).toBeInTheDocument()
+ })
+
+ // 중단된 면접은 피드백이 없다 — 문답 기록이 유일한 결과물이다.
+ it('중단 세션은 피드백 링크 없이 기록을 보여준다', () => {
+ renderPanel('INTERRUPTED')
+
+ expect(screen.queryByRole('link', { name: '피드백 보기' })).not.toBeInTheDocument()
+ expect(screen.getByTestId('transcript')).toBeInTheDocument()
+ })
+
+ // 시작 전 취소라 보여줄 문답이 없다.
+ it('취소 세션은 기록을 보여주지 않는다', () => {
+ renderPanel('CANCELLED')
+
+ expect(screen.queryByTestId('transcript')).not.toBeInTheDocument()
+ })
+
+ it('같은 설정으로 다시 누르면 원본 세션 id 로 재도전한다', async () => {
+ renderPanel('INTERRUPTED')
+
+ await userEvent.click(screen.getByRole('button', { name: '같은 설정으로 다시' }))
+
+ expect(retryMutate).toHaveBeenCalledWith(7)
+ })
+
+ // 자료 수를 넘겨야 "삭제된 자료 N개 제외" 안내가 가능하다.
+ it('원본 세션의 자료 수를 재도전 훅에 넘긴다', () => {
+ render(
+
+
+ ,
+ )
+
+ expect(lastSourceCount).toHaveBeenCalledWith(3)
+ })
+})
diff --git a/frontend/src/features/interview/ui/live/SessionEndedPanel.tsx b/frontend/src/features/interview/ui/live/SessionEndedPanel.tsx
index 7f9f2118..8025df9c 100644
--- a/frontend/src/features/interview/ui/live/SessionEndedPanel.tsx
+++ b/frontend/src/features/interview/ui/live/SessionEndedPanel.tsx
@@ -1,7 +1,8 @@
import { Link } from 'react-router-dom'
import { Button } from '@/shared/ui/Button'
import { Eyebrow } from '@/shared/ui'
-import type { SessionStatus } from '@/domain/session'
+import type { Session, SessionStatus } from '@/domain/session'
+import { useRetrySession } from '../../model/useRetrySession'
import { InterviewTranscript } from '../InterviewTranscript'
const messageByStatus: Partial> = {
@@ -19,10 +20,14 @@ const showsTranscript = (status: SessionStatus) =>
export function SessionEndedPanel({
status,
sessionId,
+ session,
}: {
status: SessionStatus
sessionId: number
+ session?: Session
}) {
+ const retry = useRetrySession(session?.contextDocumentIds?.length)
+
return (
@@ -39,6 +44,14 @@ export function SessionEndedPanel({
)}
+ {/* 중단됐든 끝났든, 같은 조건으로 한 번 더 해보는 게 다음 행동이다. */}
+
diff --git a/frontend/src/pages/SessionFeedback/ui/SessionFeedbackPage.tsx b/frontend/src/pages/SessionFeedback/ui/SessionFeedbackPage.tsx
index 30cd78a8..ca6bbd60 100644
--- a/frontend/src/pages/SessionFeedback/ui/SessionFeedbackPage.tsx
+++ b/frontend/src/pages/SessionFeedback/ui/SessionFeedbackPage.tsx
@@ -9,13 +9,16 @@ import {
useFeedback,
useRegenerateFeedback,
} from '@/features/feedback'
-import { InterviewTranscript } from '@/features/interview'
+import { InterviewTranscript, useRetrySession, useSession } from '@/features/interview'
export default function SessionFeedbackPage() {
const { id } = useParams<{ id: string }>()
const sessionId = Number(id)
const { data, isLoading, isError, error, refetch } = useFeedback(sessionId)
const regenerate = useRegenerateFeedback(sessionId)
+ // 재도전은 원본 세션의 자료 수를 알아야 "몇 개가 빠졌는지" 안내할 수 있다.
+ const { data: session } = useSession(sessionId)
+ const retry = useRetrySession(session?.contextDocumentIds?.length)
return (
@@ -26,9 +29,14 @@ export default function SessionFeedbackPage() {
title="면접 피드백"
description="점수 옆에 그렇게 매긴 근거가 함께 붙습니다."
actions={
-
-
-
+
+
+
+
+
+
}
/>
diff --git a/frontend/src/shared/api/generated.ts b/frontend/src/shared/api/generated.ts
index 4860eb61..6ed68880 100644
--- a/frontend/src/shared/api/generated.ts
+++ b/frontend/src/shared/api/generated.ts
@@ -80,6 +80,26 @@ export interface paths {
patch?: never;
trace?: never;
};
+ "/api/sessions/{sessionId}/retry": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /**
+ * 같은 설정으로 다시 면접 (US-13 재도전)
+ * @description 기존 세션의 설정(모드·직군·질문 수·JD 등)을 그대로 복사해 새 세션을 만든다. 연결 자료는 지금도 살아있고 분석이 끝난 것만 다시 잇는다 — 그 사이 삭제된 자료 때문에 재도전 전체가 404 로 막히지 않게 하기 위함. 응답의 contextDocumentIds 를 원본과 비교하면 무엇이 빠졌는지 알 수 있다.
+ */
+ post: operations["retrySession"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/api/sessions/{sessionId}/messages": {
parameters: {
query?: never;
@@ -1711,6 +1731,46 @@ export interface operations {
};
};
};
+ retrySession: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ sessionId: number;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description 새 세션 생성 완료 */
+ 201: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "*/*": components["schemas"]["SessionResponse"];
+ };
+ };
+ /** @description 인증 실패 */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "*/*": components["schemas"]["SessionResponse"];
+ };
+ };
+ /** @description 원본 세션 없음 */
+ 404: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "*/*": components["schemas"]["SessionResponse"];
+ };
+ };
+ };
+ };
listSessionMessages: {
parameters: {
query?: never;