Buckets:
| import { describe, expect } from "bun:test" | |
| import { Effect, Exit, Fiber, Layer } from "effect" | |
| import { PermissionV2 } from "@opencode-ai/core/permission" | |
| import { QuestionV2 } from "@opencode-ai/core/question" | |
| import { SessionV2 } from "@opencode-ai/core/session" | |
| import { ToolRegistry } from "@opencode-ai/core/tool/registry" | |
| import { QuestionTool } from "@opencode-ai/core/tool/question" | |
| import { testEffect } from "./lib/effect" | |
| import { toolIdentity, executeTool, settleTool, toolDefinitions } from "./lib/tool" | |
| const sessionID = SessionV2.ID.make("ses_question_tool_test") | |
| const assertions: PermissionV2.AssertInput[] = [] | |
| let captured: QuestionV2.AskInput | undefined | |
| let reject = false | |
| let deny = false | |
| const capturedInput = () => captured | |
| const permission = Layer.succeed( | |
| PermissionV2.Service, | |
| PermissionV2.Service.of({ | |
| assert: (input) => | |
| Effect.sync(() => assertions.push(input)).pipe( | |
| Effect.andThen(deny ? Effect.fail(new PermissionV2.DeniedError({ rules: [] })) : Effect.void), | |
| ), | |
| ask: () => Effect.die("unused"), | |
| reply: () => Effect.die("unused"), | |
| get: () => Effect.die("unused"), | |
| forSession: () => Effect.die("unused"), | |
| list: () => Effect.die("unused"), | |
| }), | |
| ) | |
| const registry = ToolRegistry.defaultLayer.pipe(Layer.provide(permission)) | |
| const question = Layer.succeed( | |
| QuestionV2.Service, | |
| QuestionV2.Service.of({ | |
| ask: (input: QuestionV2.AskInput) => | |
| Effect.sync(() => { | |
| captured = input | |
| }).pipe(Effect.andThen(reject ? Effect.fail(new QuestionV2.RejectedError()) : Effect.succeed([["Build"], []]))), | |
| reply: () => Effect.die("unused"), | |
| reject: () => Effect.die("unused"), | |
| list: () => Effect.die("unused"), | |
| }), | |
| ) | |
| const tool = QuestionTool.layer.pipe(Layer.provide(registry), Layer.provide(permission), Layer.provide(question)) | |
| const it = testEffect(Layer.mergeAll(permission, registry, question, tool)) | |
| describe("QuestionTool", () => { | |
| it.effect("omits a denied built-in question and terminally settles a stale call", () => | |
| Effect.gen(function* () { | |
| captured = undefined | |
| deny = true | |
| const registry = yield* ToolRegistry.Service | |
| expect(yield* toolDefinitions(registry, [{ action: "question", resource: "*", effect: "deny" }])).toEqual([]) | |
| expect( | |
| yield* settleTool(registry, { | |
| sessionID, | |
| ...toolIdentity, | |
| call: { type: "tool-call", id: "call-question-denied", name: "question", input: { questions: [] } }, | |
| }), | |
| ).toEqual({ result: { type: "error", value: "Permission denied: question" } }) | |
| expect(capturedInput()).toBeUndefined() | |
| deny = false | |
| }), | |
| ) | |
| it.effect("registers question and projects user answers without a permission assertion", () => | |
| Effect.gen(function* () { | |
| assertions.length = 0 | |
| captured = undefined | |
| reject = false | |
| deny = false | |
| const registry = yield* ToolRegistry.Service | |
| const questions = [ | |
| { | |
| question: "What should happen?", | |
| header: "Action", | |
| options: [{ label: "Build", description: "Build it" }], | |
| }, | |
| { | |
| question: "Which environment?", | |
| header: "Environment", | |
| options: [{ label: "Dev", description: "Development" }], | |
| }, | |
| ] | |
| expect((yield* toolDefinitions(registry)).map((definition) => definition.name)).toEqual(["question"]) | |
| expect( | |
| yield* settleTool(registry, { | |
| sessionID, | |
| ...toolIdentity, | |
| call: { type: "tool-call", id: "call-question", name: "question", input: { questions } }, | |
| }), | |
| ).toEqual({ | |
| result: { | |
| type: "text", | |
| value: | |
| 'User has answered your questions: "What should happen?"="Build", "Which environment?"="Unanswered". You can now continue with the user\'s answers in mind.', | |
| }, | |
| output: { | |
| structured: { answers: [["Build"], []] }, | |
| content: [ | |
| { | |
| type: "text", | |
| text: 'User has answered your questions: "What should happen?"="Build", "Which environment?"="Unanswered". You can now continue with the user\'s answers in mind.', | |
| }, | |
| ], | |
| }, | |
| }) | |
| expect(assertions).toMatchObject([{ sessionID, action: "question", resources: ["*"] }]) | |
| expect(capturedInput()).toEqual({ | |
| sessionID, | |
| questions, | |
| tool: { messageID: toolIdentity.assistantMessageID, callID: "call-question" }, | |
| }) | |
| }), | |
| ) | |
| it.effect("does not invent tool ownership metadata without a durable registry source", () => | |
| Effect.gen(function* () { | |
| captured = undefined | |
| reject = false | |
| deny = false | |
| const registryService = yield* ToolRegistry.Service | |
| yield* executeTool(registryService, { | |
| sessionID, | |
| ...toolIdentity, | |
| call: { type: "tool-call", id: "call-question", name: "question", input: { questions: [] } }, | |
| }) | |
| expect(capturedInput()).toEqual({ | |
| sessionID, | |
| questions: [], | |
| tool: { messageID: toolIdentity.assistantMessageID, callID: "call-question" }, | |
| }) | |
| }), | |
| ) | |
| it.effect("keeps dismissed questions out of model-facing output", () => | |
| Effect.gen(function* () { | |
| captured = undefined | |
| reject = true | |
| deny = false | |
| const registryService = yield* ToolRegistry.Service | |
| const fiber = yield* executeTool(registryService, { | |
| sessionID, | |
| ...toolIdentity, | |
| call: { type: "tool-call", id: "call-question", name: "question", input: { questions: [] } }, | |
| }).pipe(Effect.forkScoped) | |
| const exit = yield* Fiber.await(fiber) | |
| expect(Exit.isFailure(exit)).toBe(true) | |
| }), | |
| ) | |
| }) | |
Xet Storage Details
- Size:
- 5.72 kB
- Xet hash:
- 6cc0544ccbf470433eeae5584f35fa0c5e686b3d67772b8b383eceb94767c7c8
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.