173 lines · 5.4 KB · TypeScript
RawDownload
import type { ProviderUserInputAnswers, UserInputQuestion } from "@t3tools/contracts";import * as Schema from "effect/Schema"; const XAiAskUserQuestionOption = Schema.Struct({  label: Schema.String,  description: Schema.optional(Schema.String),  preview: Schema.optional(Schema.String),  id: Schema.optional(Schema.String),}); const XAiAskUserQuestion = Schema.Struct({  id: Schema.optional(Schema.String),  question: Schema.String,  options: Schema.Array(XAiAskUserQuestionOption),  multiSelect: Schema.optional(Schema.NullOr(Schema.Boolean)),}); const XAiAskUserQuestionParams = Schema.Struct({  sessionId: Schema.String,  toolCallId: Schema.String,  questions: Schema.Array(XAiAskUserQuestion),  mode: Schema.Literals(["default", "plan"]),}); const XAiWrappedAskUserQuestionParams = Schema.Struct({  method: Schema.Literals(["x.ai/ask_user_question", "_x.ai/ask_user_question"]),  params: XAiAskUserQuestionParams,}); export const XAiAskUserQuestionRequest = Schema.Union([  XAiAskUserQuestionParams,  XAiWrappedAskUserQuestionParams,]); type XAiAskUserQuestionRequestParams = typeof XAiAskUserQuestionParams.Type;type XAiAskUserQuestionRequest = typeof XAiAskUserQuestionRequest.Type; function trimmed(value: string | undefined): string | undefined {  const text = value?.trim();  return text && text.length > 0 ? text : undefined;} function unwrapAskUserQuestionParams(  params: XAiAskUserQuestionRequest,): XAiAskUserQuestionRequestParams {  return "params" in params ? params.params : params;} export function extractXAiAskUserQuestions(  params: XAiAskUserQuestionRequest,): ReadonlyArray<UserInputQuestion> {  return unwrapAskUserQuestionParams(params).questions.map((question) => ({    id: question.id ?? question.question,    header: "Question",    question: question.question,    multiSelect: question.multiSelect === true,    options:      question.options.length > 0        ? question.options.map((option) => ({            label: option.label,            description: option.description ?? option.label,          }))        : [{ label: "OK", description: "Continue" }],  }));} interface XAiAskUserQuestionAnnotation {  readonly preview?: string;  readonly notes?: string;} interface XAiAskUserQuestionAcceptedResponse {  readonly outcome: "accepted";  readonly answers: Record<string, ReadonlyArray<string>>;  readonly annotations?: Record<string, XAiAskUserQuestionAnnotation>;} interface XAiAskUserQuestionCancelledResponse {  readonly outcome: "cancelled";} export type XAiAskUserQuestionResponse =  | XAiAskUserQuestionAcceptedResponse  | XAiAskUserQuestionCancelledResponse; interface NormalizedXAiAnswer {  readonly questionText: string;  readonly selectedLabels: ReadonlyArray<string>;  readonly annotation?: XAiAskUserQuestionAnnotation;} function answerValues(answer: unknown): ReadonlyArray<string> {  if (Array.isArray(answer)) {    return answer.flatMap((entry) => {      const text = typeof entry === "string" ? trimmed(entry) : undefined;      return text ? [text] : [];    });  }  const text = typeof answer === "string" ? trimmed(answer) : undefined;  return text ? [text] : [];} function normalizeAnswerForXAi(  question: XAiAskUserQuestionRequestParams["questions"][number],  answer: unknown,): NormalizedXAiAnswer | undefined {  const values = answerValues(answer);  if (values.length === 0) {    return undefined;  }   const optionByLabel = new Map(question.options.map((option) => [option.label, option]));  const resolvedValues = values.map((value) => ({    value,    option: optionByLabel.get(value),  }));  const selectedLabels = resolvedValues.flatMap(({ option }) => (option ? [option.label] : []));  const notes = resolvedValues.flatMap(({ option, value }) => (option ? [] : [value]));  const preview =    question.multiSelect === true      ? undefined      : resolvedValues.map(({ option }) => trimmed(option?.preview)).find((value) => value);   const annotation =    preview || notes.length > 0      ? {          ...(preview ? { preview } : {}),          ...(notes.length > 0 ? { notes: notes.join("\n") } : {}),        }      : undefined;   return {    questionText: question.question,    selectedLabels: selectedLabels.length > 0 ? selectedLabels : ["Other"],    ...(annotation ? { annotation } : {}),  };} function findQuestionAnswer(  answers: ProviderUserInputAnswers,  question: XAiAskUserQuestionRequestParams["questions"][number],): unknown {  const key = question.id ?? question.question;  return answers[key] ?? answers[question.question];} export function makeXAiAskUserQuestionResponse(  params: XAiAskUserQuestionRequest,  answers: ProviderUserInputAnswers,): XAiAskUserQuestionAcceptedResponse {  const questions = unwrapAskUserQuestionParams(params).questions;  const normalized = questions.flatMap((question) => {    const entry = normalizeAnswerForXAi(question, findQuestionAnswer(answers, question));    return entry ? [entry] : [];  });  const annotations = Object.fromEntries(    normalized.flatMap((entry) =>      entry.annotation ? [[entry.questionText, entry.annotation] as const] : [],    ),  );   return {    outcome: "accepted",    answers: Object.fromEntries(      normalized.map((entry) => [entry.questionText, entry.selectedLabels]),    ),    ...(Object.keys(annotations).length > 0 ? { annotations } : {}),  };} export function makeXAiAskUserQuestionCancelledResponse(): XAiAskUserQuestionCancelledResponse {  return { outcome: "cancelled" };}