86 lines
3.1 KiB
TypeScript
86 lines
3.1 KiB
TypeScript
export * as QuestionTool from "./question"
|
|
|
|
import { ToolFailure } from "@opencode-ai/llm"
|
|
import { Effect, Layer, Schema } from "effect"
|
|
import { PermissionV2 } from "../permission"
|
|
import { QuestionV2 } from "../question"
|
|
import { Tool } from "./tool"
|
|
import { Tools } from "./tools"
|
|
|
|
export const name = "question"
|
|
|
|
export const description = `Use this tool when you need to ask the user questions during execution. This allows you to:
|
|
1. Gather user preferences or requirements
|
|
2. Clarify ambiguous instructions
|
|
3. Get decisions on implementation choices as you work
|
|
4. Offer choices to the user about what direction to take.
|
|
|
|
Usage notes:
|
|
- When \`custom\` is enabled (default), a "Type your own answer" option is added automatically; don't include "Other" or catch-all options
|
|
- Answers are returned as arrays of labels; set \`multiple: true\` to allow selecting more than one
|
|
- If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label`
|
|
|
|
export const Input = Schema.Struct({
|
|
questions: Schema.Array(QuestionV2.Prompt).annotate({ description: "Questions to ask" }),
|
|
})
|
|
|
|
export const Output = Schema.Struct({
|
|
answers: Schema.Array(QuestionV2.Answer),
|
|
})
|
|
export type Output = typeof Output.Type
|
|
|
|
export const toModelOutput = (
|
|
questions: ReadonlyArray<QuestionV2.Prompt>,
|
|
answers: ReadonlyArray<QuestionV2.Answer>,
|
|
) => {
|
|
const formatted = questions
|
|
.map(
|
|
(question, index) =>
|
|
`"${question.question}"="${answers[index]?.length ? answers[index].join(", ") : "Unanswered"}"`,
|
|
)
|
|
.join(", ")
|
|
return `User has answered your questions: ${formatted}. You can now continue with the user's answers in mind.`
|
|
}
|
|
|
|
export const layer = Layer.effectDiscard(
|
|
Effect.gen(function* () {
|
|
const tools = yield* Tools.Service
|
|
const question = yield* QuestionV2.Service
|
|
const permission = yield* PermissionV2.Service
|
|
|
|
yield* tools
|
|
.register({
|
|
[name]: Tool.make({
|
|
description,
|
|
input: Input,
|
|
output: Output,
|
|
toModelOutput: ({ input, output }) => [
|
|
{ type: "text", text: toModelOutput(input.questions, output.answers) },
|
|
],
|
|
execute: (input, context) =>
|
|
permission
|
|
.assert({
|
|
action: "question",
|
|
resources: ["*"],
|
|
sessionID: context.sessionID,
|
|
agent: context.agent,
|
|
source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
|
|
})
|
|
.pipe(
|
|
Effect.mapError(() => new ToolFailure({ message: "Permission denied: question" })),
|
|
Effect.andThen(
|
|
question
|
|
.ask({
|
|
sessionID: context.sessionID,
|
|
questions: input.questions,
|
|
tool: { messageID: context.assistantMessageID, callID: context.toolCallID },
|
|
})
|
|
.pipe(Effect.orDie),
|
|
),
|
|
Effect.map((answers) => ({ answers })),
|
|
),
|
|
}),
|
|
})
|
|
.pipe(Effect.orDie)
|
|
}),
|
|
)
|