import { NodeId, RuntimeRequestId, TurnItemId, type OrchestrationV2ThreadProjection,} from "@t3tools/contracts";import { describe, expect, it } from "vite-plus/test";import { v2Now, v2Projection } from "./orchestrationV2TestFixtures.ts";import { derivePendingThreadRequests } from "./threadRequests.ts"; const requestId = RuntimeRequestId.make("async-question");const nodeId = NodeId.make("async-question-node");const projection: OrchestrationV2ThreadProjection = { ...v2Projection, runtimeRequests: [ { id: requestId, nodeId, providerTurnId: null, nativeRequestRef: null, kind: "user_input", status: "pending", responseCapability: { type: "message" }, createdAt: v2Now, resolvedAt: null, }, ], turnItems: [ { id: TurnItemId.make("async-question-item"), threadId: v2Projection.thread.id, runId: null, nodeId, providerThreadId: null, providerTurnId: null, nativeItemRef: null, parentItemId: null, ordinal: 0, status: "completed", title: null, startedAt: v2Now, completedAt: v2Now, updatedAt: v2Now, type: "user_input_request", requestId, responseMode: "message", questions: [ { id: "next", header: "Next", question: "What should happen next?", required: true, allowCustomAnswer: false, options: [{ label: "Continue", value: " continue ", description: "Resume work" }], }, ], }, ],}; describe("pending v2 questions", () => { it("keeps message responses available after the originating runtime exits", () => { expect(projection.providerSessions).toEqual([]); expect(derivePendingThreadRequests(projection).userInputs).toEqual([ { requestId, createdAt: "2026-06-20T00:00:00.000Z", responseCapability: "message", responseMode: "message", questions: [ { id: "next", header: "Next", question: "What should happen next?", required: true, allowCustomAnswer: false, multiSelect: false, options: [{ label: "Continue", value: " continue ", description: "Resume work" }], }, ], }, ]); }); it("removes answered requests from the composer while retaining their answers in projection data", () => { const answered = { ...projection, runtimeRequests: projection.runtimeRequests.map((request) => ({ ...request, status: "resolved" as const, resolvedAt: v2Now, answers: { next: " continue " }, })), }; expect(derivePendingThreadRequests(answered).userInputs).toEqual([]); expect(answered.runtimeRequests[0]?.answers).toEqual({ next: " continue " }); });});
import {
NodeId,
RuntimeRequestId,
TurnItemId,
type OrchestrationV2ThreadProjection,
} from "@t3tools/contracts";
import { describe, expect, it } from "vite-plus/test";
import { v2Now, v2Projection } from "./orchestrationV2TestFixtures.ts";
import { derivePendingThreadRequests } from "./threadRequests.ts";
const requestId = RuntimeRequestId.make("async-question");
const nodeId = NodeId.make("async-question-node");
const projection: OrchestrationV2ThreadProjection = {
...v2Projection,
runtimeRequests: [
{
id: requestId,
nodeId,
providerTurnId: null,
nativeRequestRef: null,
kind: "user_input",
status: "pending",
responseCapability: { type: "message" },
createdAt: v2Now,
resolvedAt: null,
},
],
turnItems: [
{
id: TurnItemId.make("async-question-item"),
threadId: v2Projection.thread.id,
runId: null,
nodeId,
providerThreadId: null,
providerTurnId: null,
nativeItemRef: null,
parentItemId: null,
ordinal: 0,
status: "completed",
title: null,
startedAt: v2Now,
completedAt: v2Now,
updatedAt: v2Now,
type: "user_input_request",
requestId,
responseMode: "message",
questions: [
{
id: "next",
header: "Next",
question: "What should happen next?",
required: true,
allowCustomAnswer: false,
options: [{ label: "Continue", value: " continue ", description: "Resume work" }],
},
],
},
],
};
describe("pending v2 questions", () => {
it("keeps message responses available after the originating runtime exits", () => {
expect(projection.providerSessions).toEqual([]);
expect(derivePendingThreadRequests(projection).userInputs).toEqual([
{
requestId,
createdAt: "2026-06-20T00:00:00.000Z",
responseCapability: "message",
responseMode: "message",
questions: [
{
id: "next",
header: "Next",
question: "What should happen next?",
required: true,
allowCustomAnswer: false,
multiSelect: false,
options: [{ label: "Continue", value: " continue ", description: "Resume work" }],
},
],
},
]);
});
it("removes answered requests from the composer while retaining their answers in projection data", () => {
const answered = {
...projection,
runtimeRequests: projection.runtimeRequests.map((request) => ({
...request,
status: "resolved" as const,
resolvedAt: v2Now,
answers: { next: " continue " },
})),
};
expect(derivePendingThreadRequests(answered).userInputs).toEqual([]);
expect(answered.runtimeRequests[0]?.answers).toEqual({ next: " continue " });
});
});