Phải huỷ cả ba tầng, thiếu một tầng là vẫn tốn tiền.
1. Client dừng đọc. AbortController huỷ fetch tới route handler của bạn.
2. Server nhận biết client đã ngắt. Route handler dùng req.signal — signal này bắn khi kết nối đứt.
3. Server huỷ request tới nhà cung cấp. Truyền tiếp signal xuống lời gọi SDK. Đây là bước hay bị quên: client đóng tab nhưng server vẫn đọc hết stream từ provider và vẫn bị tính đủ token đầu ra.
export async function POST(req: Request) {
const result = await client.messages.stream(
{ model, messages },
{ signal: req.signal }, // forward cancellation to the provider
)
return result.toResponse()
}Những điểm cần xử lý thêm:
- Vẫn lưu phần đã sinh ra. Người dùng dừng vì đã đọc đủ; lưu phần dở dang kèm cờ stopped để hội thoại sau vẫn mạch lạc.
- Vẫn ghi nhận chi phí. Token đầu vào đã tính đủ, token đầu ra tính theo phần đã phát — vẫn phải trừ vào hạn mức người dùng.
- Huỷ không phải lỗi. Bắt riêng AbortError để không đẩy vào cảnh báo lỗi và không kích hoạt retry.