Compare commits

...
Author SHA1 Message Date
Kit Langton 2e8c67f2be test(core): construct valid tool contexts 2026-08-27 15:18:55 -04:00
2 changed files with 28 additions and 23 deletions
+2 -2
View File
@@ -5,7 +5,7 @@ import { LayerNode } from "@opencode-ai/util/effect/layer-node"
import { Bus } from "@opencode-ai/core/bus"
import { Location } from "@opencode-ai/core/location"
import { Pty } from "@opencode-ai/core/pty"
import type { PtyID } from "@opencode-ai/core/pty/schema"
import { PtyID } from "@opencode-ai/core/pty/schema"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { ShellSelect } from "@opencode-ai/core/shell/select"
import { location } from "../fixture/location"
@@ -88,7 +88,7 @@ describe("pty", () => {
it.live("returns typed not found errors for missing sessions", () =>
Effect.gen(function* () {
const pty = yield* Pty.Service
const id = "pty_missing" as PtyID
const id = PtyID.make("pty_missing")
for (const result of [
yield* pty.get(id).pipe(Effect.asVoid, Effect.exit),
+26 -21
View File
@@ -1,10 +1,21 @@
import { expect, test } from "bun:test"
import { Effect, Schema } from "effect"
import { z } from "zod"
import { Agent } from "@opencode-ai/schema/agent"
import { Session } from "@opencode-ai/schema/session"
import { SessionMessage } from "@opencode-ai/schema/session-message"
import type { Info } from "@opencode-ai/schema/tool"
import { Tool } from "../src/tool"
import { definition, execute } from "../src/tool/runtime"
const context = {
sessionID: Session.ID.make("ses_tool_schema"),
agent: Agent.ID.make("build"),
messageID: SessionMessage.ID.make("msg_tool_schema"),
id: Tool.CallID.make("call_tool_schema"),
progress: () => Effect.void,
} satisfies Tool.Context
test("tools are structural values", async () => {
const config = {
name: "foreign",
@@ -136,7 +147,7 @@ test("portable schemas validate and describe typed tools", async () => {
inputSchema: { type: "object", properties: { count: { type: "string" } } },
outputSchema: { type: "string" },
})
const result = await Effect.runPromise(execute(tool, { count: "41" }, {} as Tool.Context))
const result = await Effect.runPromise(execute(tool, { count: "41" }, context))
expect(result.output).toBe("42")
})
@@ -166,10 +177,10 @@ test("Zod schemas validate, transform, and describe typed tools", async () => {
additionalProperties: false,
},
})
expect(await Effect.runPromise(execute(tool, { count: "41" }, {} as Tool.Context))).toMatchObject({
expect(await Effect.runPromise(execute(tool, { count: "41" }, context))).toMatchObject({
output: { count: 42 },
})
expect(await Effect.runPromise(Effect.flip(execute(tool, { count: 41 }, {} as Tool.Context)))).toEqual(
expect(await Effect.runPromise(Effect.flip(execute(tool, { count: 41 }, context)))).toEqual(
new Tool.Error({
message:
'Invalid arguments for tool "zod":\n- count: Invalid input: expected string, received number\n\nArguments provided:\n{\n "count": 41\n}\n\nUpdate the arguments and call the tool again.',
@@ -205,7 +216,7 @@ test("portable schema failures become tool failures", async () => {
execute: () => Effect.succeed({ content: "unused" }),
},
1,
{} as Tool.Context,
context,
),
),
)
@@ -228,9 +239,7 @@ test("Effect schema failures use normalized input issues", async () => {
execute: () => Effect.succeed({ content: "unused" }),
}
expect(
await Effect.runPromise(Effect.flip(execute(tool, { value: 1, nested: { count: 0 } }, {} as Tool.Context))),
).toEqual(
expect(await Effect.runPromise(Effect.flip(execute(tool, { value: 1, nested: { count: 0 } }, context)))).toEqual(
new Tool.Error({
message:
'Invalid arguments for tool "effect":\n- value: Expected string\n- nested.count: Expected a value greater than or equal to 1\n\nArguments provided:\n{\n "value": 1,\n "nested": {\n "count": 0\n }\n}\n\nUpdate the arguments and call the tool again.',
@@ -259,7 +268,7 @@ test("input error prompts limit normalized issues", async () => {
execute: () => Effect.succeed({ content: "unused" }),
}
expect(await Effect.runPromise(Effect.flip(execute(tool, {}, {} as Tool.Context)))).toEqual(
expect(await Effect.runPromise(Effect.flip(execute(tool, {}, context)))).toEqual(
new Tool.Error({
message:
'Invalid arguments for tool "limited":\n- root: issue 1\n- root: issue 2\n- root: issue 3\n- root: issue 4\n- root: issue 5\n- ...and 1 more issue\n\nArguments provided:\n{}\n\nUpdate the arguments and call the tool again.',
@@ -278,7 +287,7 @@ test("canonical results carry metadata with typed output", async () => {
execute: ({ value }) => Effect.succeed({ output: { value, internal: true }, metadata: { value }, content: value }),
}
expect(await Effect.runPromise(tool.execute({ value: "out" }, {} as Tool.Context))).toEqual({
expect(await Effect.runPromise(tool.execute({ value: "out" }, context))).toEqual({
output: { value: "out", internal: true },
metadata: { value: "out" },
content: "out",
@@ -312,33 +321,29 @@ test("raw JSON schemas validate and decode tool input", async () => {
description: "Raw tool",
inputSchema: input,
})
expect(await Effect.runPromise(execute(tool, { value: "ok", extra: true }, {} as Tool.Context))).toEqual({
expect(await Effect.runPromise(execute(tool, { value: "ok", extra: true }, context))).toEqual({
output: undefined,
content: [{ type: "text", text: '{"value":"ok"}' }],
})
expect(await Effect.runPromise(Effect.flip(execute(tool, { value: 1 }, {} as Tool.Context)))).toEqual(
expect(await Effect.runPromise(Effect.flip(execute(tool, { value: 1 }, context)))).toEqual(
new Tool.Error({
message:
'Invalid arguments for tool "raw":\n- value: Expected string\n\nArguments provided:\n{\n "value": 1\n}\n\nUpdate the arguments and call the tool again.',
}),
)
expect(await Effect.runPromise(Effect.flip(execute(tool, {}, {} as Tool.Context)))).toEqual(
expect(await Effect.runPromise(Effect.flip(execute(tool, {}, context)))).toEqual(
new Tool.Error({
message:
'Invalid arguments for tool "raw":\n- value: Missing key\n\nArguments provided:\n{}\n\nUpdate the arguments and call the tool again.',
}),
)
expect(
await Effect.runPromise(Effect.flip(execute(tool, { value: "ok", nested: { count: 0 } }, {} as Tool.Context))),
).toEqual(
expect(await Effect.runPromise(Effect.flip(execute(tool, { value: "ok", nested: { count: 0 } }, context)))).toEqual(
new Tool.Error({
message:
'Invalid arguments for tool "raw":\n- nested.count: Expected a value greater than or equal to 1\n\nArguments provided:\n{\n "value": "ok",\n "nested": {\n "count": 0\n }\n}\n\nUpdate the arguments and call the tool again.',
}),
)
expect(
await Effect.runPromise(Effect.flip(execute(tool, { value: 1, nested: { count: 0 } }, {} as Tool.Context))),
).toEqual(
expect(await Effect.runPromise(Effect.flip(execute(tool, { value: 1, nested: { count: 0 } }, context)))).toEqual(
new Tool.Error({
message:
'Invalid arguments for tool "raw":\n- value: Expected string\n- nested.count: Expected a value greater than or equal to 1\n\nArguments provided:\n{\n "value": 1,\n "nested": {\n "count": 0\n }\n}\n\nUpdate the arguments and call the tool again.',
@@ -359,10 +364,10 @@ test("raw JSON schemas resolve draft-07 definitions", async () => {
execute: (input) => Effect.succeed({ content: JSON.stringify(input) }),
}
expect(await Effect.runPromise(execute(tool, { value: "ok" }, {} as Tool.Context))).toMatchObject({
expect(await Effect.runPromise(execute(tool, { value: "ok" }, context))).toMatchObject({
content: [{ type: "text", text: '{"value":"ok"}' }],
})
expect(await Effect.runPromise(Effect.flip(execute(tool, { value: 1 }, {} as Tool.Context)))).toEqual(
expect(await Effect.runPromise(Effect.flip(execute(tool, { value: 1 }, context)))).toEqual(
new Tool.Error({
message:
'Invalid arguments for tool "draft-07":\n- value: Expected value\n\nArguments provided:\n{\n "value": 1\n}\n\nUpdate the arguments and call the tool again.',
@@ -381,7 +386,7 @@ test("raw JSON schemas pass input through when they cannot be imported", async (
execute: (input) => Effect.succeed({ content: JSON.stringify(input) }),
}
expect(await Effect.runPromise(execute(tool, { value: 1, extra: true }, {} as Tool.Context))).toMatchObject({
expect(await Effect.runPromise(execute(tool, { value: 1, extra: true }, context))).toMatchObject({
content: [{ type: "text", text: '{"value":1,"extra":true}' }],
})
})