turn.ts
1,510 bytes
| 1 | import type { AnswerResponse, InterviewTurn } from '../../shared/types' |
|---|---|
| 2 | import type { InterviewLlm } from '../providers/types' |
| 3 | import type { SessionStore } from '../store/sessionStore' |
| 4 | import { isDoneAnswer, runInterviewTurn } from './interview' |
| 5 | |
| 6 | export class SessionNotFoundError extends Error { |
| 7 | constructor(sessionId: string) { |
| 8 | super(`session not found: ${sessionId}`) |
| 9 | } |
| 10 | } |
| 11 | |
| 12 | export async function submitAnswer( |
| 13 | store: SessionStore, |
| 14 | llm: InterviewLlm, |
| 15 | sessionId: string, |
| 16 | text: string, |
| 17 | ): Promise<AnswerResponse> { |
| 18 | const existing = await store.getSession(sessionId) |
| 19 | if (!existing) throw new SessionNotFoundError(sessionId) |
| 20 | |
| 21 | const { session: afterAnswer, segment } = await store.appendSegment(sessionId, 'user', text) |
| 22 | |
| 23 | let turn: InterviewTurn |
| 24 | let remainingBlockers = afterAnswer.openBlockers |
| 25 | |
| 26 | if (!isDoneAnswer(text) && afterAnswer.openBlockers.length > 0) { |
| 27 | const [nextQuestion, ...rest] = afterAnswer.openBlockers |
| 28 | remainingBlockers = rest |
| 29 | turn = { |
| 30 | coverage: afterAnswer.coverage, |
| 31 | nextQuestion, |
| 32 | contradiction: null, |
| 33 | done: false, |
| 34 | summaryUpdate: null, |
| 35 | } |
| 36 | } else { |
| 37 | turn = await runInterviewTurn(llm, afterAnswer) |
| 38 | } |
| 39 | |
| 40 | await store.appendSegment(sessionId, 'interviewer', turn.nextQuestion) |
| 41 | await store.updateTurn(sessionId, { |
| 42 | coverage: turn.coverage, |
| 43 | summary: turn.summaryUpdate ?? afterAnswer.summary, |
| 44 | status: turn.done ? 'done' : 'interviewing', |
| 45 | openBlockers: remainingBlockers, |
| 46 | }) |
| 47 | |
| 48 | return { segment, turn } |
| 49 | } |
| 50 | |