|
|
@@ -8,16 +8,20 @@ import { QuestionID } from "./schema"
|
|
|
|
|
|
const log = Log.create({ service: "question" })
|
|
|
|
|
|
-// Schemas
|
|
|
+// Schemas — these are pure data; nothing checks class identity (see PR
|
|
|
+// description) so they're plain `Schema.Struct` + type alias. That lets
|
|
|
+// `Question.ask` and other internal sites trust the type contract without a
|
|
|
+// re-decode to coerce nested class instances.
|
|
|
|
|
|
-export class Option extends Schema.Class<Option>("QuestionOption")({
|
|
|
+export const Option = Schema.Struct({
|
|
|
label: Schema.String.annotate({
|
|
|
description: "Display text (1-5 words, concise)",
|
|
|
}),
|
|
|
description: Schema.String.annotate({
|
|
|
description: "Explanation of choice",
|
|
|
}),
|
|
|
-}) {}
|
|
|
+}).annotate({ identifier: "QuestionOption" })
|
|
|
+export type Option = Schema.Schema.Type<typeof Option>
|
|
|
|
|
|
const base = {
|
|
|
question: Schema.String.annotate({
|
|
|
@@ -34,48 +38,53 @@ const base = {
|
|
|
}),
|
|
|
}
|
|
|
|
|
|
-export class Info extends Schema.Class<Info>("QuestionInfo")({
|
|
|
+export const Info = Schema.Struct({
|
|
|
...base,
|
|
|
custom: Schema.optional(Schema.Boolean).annotate({
|
|
|
description: "Allow typing a custom answer (default: true)",
|
|
|
}),
|
|
|
-}) {}
|
|
|
+}).annotate({ identifier: "QuestionInfo" })
|
|
|
+export type Info = Schema.Schema.Type<typeof Info>
|
|
|
|
|
|
-export class Prompt extends Schema.Class<Prompt>("QuestionPrompt")(base) {}
|
|
|
+export const Prompt = Schema.Struct(base).annotate({ identifier: "QuestionPrompt" })
|
|
|
+export type Prompt = Schema.Schema.Type<typeof Prompt>
|
|
|
|
|
|
-export class Tool extends Schema.Class<Tool>("QuestionTool")({
|
|
|
+export const Tool = Schema.Struct({
|
|
|
messageID: MessageID,
|
|
|
callID: Schema.String,
|
|
|
-}) {}
|
|
|
+}).annotate({ identifier: "QuestionTool" })
|
|
|
+export type Tool = Schema.Schema.Type<typeof Tool>
|
|
|
|
|
|
-export class Request extends Schema.Class<Request>("QuestionRequest")({
|
|
|
+export const Request = Schema.Struct({
|
|
|
id: QuestionID,
|
|
|
sessionID: SessionID,
|
|
|
questions: Schema.Array(Info).annotate({
|
|
|
description: "Questions to ask",
|
|
|
}),
|
|
|
tool: Schema.optional(Tool),
|
|
|
-}) {}
|
|
|
+}).annotate({ identifier: "QuestionRequest" })
|
|
|
+export type Request = Schema.Schema.Type<typeof Request>
|
|
|
|
|
|
export const Answer = Schema.Array(Schema.String).annotate({ identifier: "QuestionAnswer" })
|
|
|
export type Answer = Schema.Schema.Type<typeof Answer>
|
|
|
|
|
|
-export class Reply extends Schema.Class<Reply>("QuestionReply")({
|
|
|
+export const Reply = Schema.Struct({
|
|
|
answers: Schema.Array(Answer).annotate({
|
|
|
description: "User answers in order of questions (each answer is an array of selected labels)",
|
|
|
}),
|
|
|
-}) {}
|
|
|
+}).annotate({ identifier: "QuestionReply" })
|
|
|
+export type Reply = Schema.Schema.Type<typeof Reply>
|
|
|
|
|
|
-class Replied extends Schema.Class<Replied>("QuestionReplied")({
|
|
|
+const Replied = Schema.Struct({
|
|
|
sessionID: SessionID,
|
|
|
requestID: QuestionID,
|
|
|
answers: Schema.Array(Answer),
|
|
|
-}) {}
|
|
|
+}).annotate({ identifier: "QuestionReplied" })
|
|
|
|
|
|
-class Rejected extends Schema.Class<Rejected>("QuestionRejected")({
|
|
|
+const Rejected = Schema.Struct({
|
|
|
sessionID: SessionID,
|
|
|
requestID: QuestionID,
|
|
|
-}) {}
|
|
|
+}).annotate({ identifier: "QuestionRejected" })
|
|
|
|
|
|
export const Event = {
|
|
|
Asked: BusEvent.define("question.asked", Request),
|
|
|
@@ -146,26 +155,12 @@ export const layer = Layer.effect(
|
|
|
log.info("asking", { id, questions: input.questions.length })
|
|
|
|
|
|
const deferred = yield* Deferred.make<ReadonlyArray<Answer>, RejectedError>()
|
|
|
- // Use the Effect-returning decode so a schema failure surfaces as a
|
|
|
- // typed error the tool wrap can turn into a "rewrite the input" tool
|
|
|
- // result. The previous `decodeUnknownSync` would throw uncaught, which
|
|
|
- // crashed the assistant turn for any payload that slipped past the
|
|
|
- // wrap-level validation (#28438).
|
|
|
- const info = yield* Schema.decodeUnknownEffect(Request)({
|
|
|
+ const info: Request = {
|
|
|
id,
|
|
|
sessionID: input.sessionID,
|
|
|
questions: input.questions,
|
|
|
tool: input.tool,
|
|
|
- }).pipe(
|
|
|
- Effect.mapError(
|
|
|
- (error) =>
|
|
|
- new Error(
|
|
|
- `The question tool was called with invalid arguments: ${error}.\nPlease rewrite the input so it satisfies the expected schema.`,
|
|
|
- { cause: error },
|
|
|
- ),
|
|
|
- ),
|
|
|
- Effect.orDie,
|
|
|
- )
|
|
|
+ }
|
|
|
pending.set(id, { info, deferred })
|
|
|
yield* bus.publish(Event.Asked, info)
|
|
|
|