feat(consult): open the response stream before preparation, reservation and classification; rejections ride the stream (T2, BUG-1074)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0199rbQDTsUbCVw84wc8BTFe
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
3f8b817261
commit
0bc6659052
@@ -40,6 +40,7 @@ import { createServerSupabaseClient } from "@/lib/supabase/server";
|
||||
import { streamTextResponse } from "@/lib/stream-text-response";
|
||||
import { classifyConsultationTurn, type SmalltalkUsage } from "@/lib/consultation-smalltalk";
|
||||
import { streamSmalltalkResponse } from "@/lib/stream-smalltalk-response";
|
||||
import { streamFirstResponse } from "@/lib/stream-first-response";
|
||||
import { consultationPublicActivityEvent, streamAgentResponse } from "@/lib/stream-agent-response";
|
||||
import type { AgentExecutionReceipt, WorkflowReceipt } from "@/lib/consultation-agent-events";
|
||||
import {
|
||||
@@ -283,6 +284,7 @@ function workflowStatus(status: string | undefined): "ready" | "degraded" | "blo
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const requestStartedAt = Date.now();
|
||||
let supabase: Awaited<ReturnType<typeof createServerSupabaseClient>>;
|
||||
let accounting: ReturnType<typeof createAdminSupabaseClient>;
|
||||
try {
|
||||
@@ -406,6 +408,16 @@ export async function POST(request: Request) {
|
||||
type SelectedModel = NonNullable<Awaited<ReturnType<typeof resolveSessionLanguageModel>>>;
|
||||
type ReservationResult = { success: boolean; credits: number | null; error_code: string | null };
|
||||
type ModelSelection = Awaited<ReturnType<typeof reserveConsultationModel<SelectedModel, ReservationResult>>>;
|
||||
|
||||
// Everything above answered with a real HTTP status: authentication, the
|
||||
// request body, the session and its model. Everything below — subject and
|
||||
// profile reads, the credit reservation, appending the question, the model
|
||||
// classification, the agent itself — runs inside the response stream in the
|
||||
// agentic runtime (BUG-1074): `runTurn` still returns the same Responses it
|
||||
// always did, and `streamFirstResponse` relays them after a first frame that
|
||||
// goes out before any of this work starts. The legacy runtime keeps the
|
||||
// pre-stream order because its text/plain replies cannot ride an NDJSON stream.
|
||||
const runTurn = async (): Promise<Response> => {
|
||||
let prepared: PreparedConsultationRoute<ModelSelection, ConsultationPlan>;
|
||||
try {
|
||||
prepared = await prepareConsultationRoute<ModelSelection, ConsultationPlan>({
|
||||
@@ -1625,4 +1637,25 @@ export async function POST(request: Request) {
|
||||
{ status: 503 },
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (!shouldUseAgenticRuntime(user)) return runTurn();
|
||||
return streamFirstResponse({
|
||||
requestId,
|
||||
run: runTurn,
|
||||
startedAt: requestStartedAt,
|
||||
onFirstFrame: (elapsedMs) => {
|
||||
logAgentObservability({
|
||||
requestId,
|
||||
sessionId,
|
||||
agentVersion: "consultation-stream-first-v1",
|
||||
contractPhases: [{ phase: "first_byte", durationMs: elapsedMs, status: "completed" }],
|
||||
billingSettlementResult: "not_applicable",
|
||||
});
|
||||
},
|
||||
onUnhandled: (error) => {
|
||||
const reason = error instanceof Error ? error.name : "UnknownError";
|
||||
console.error(`[consult] turn failed before a response request=${requestId} reason=${reason}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user