opencode/packages/core/test/codemode.test.ts
2026-10-09 01:36:06 -07:00

95 lines
3.6 KiB
TypeScript

import { describe, expect } from "bun:test"
import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder"
import { Location } from "@opencode/core/location"
import { AbsolutePath } from "@opencode/core/schema"
import { Tool } from "@opencode/core/tool"
import { Effect, Schema } from "effect"
import type { Info } from "@opencode/schema/tool"
import { it } from "./lib/effect"
const toolNode = AppNodeBuilder.build(Tool.node, [
Location.node.replace(Location.boundNode({ directory: AbsolutePath.make("/project") })),
])
describe("CodeMode", () => {
it.effect("owns registrations, execute, and catalog materialization", () =>
Effect.gen(function* () {
const tools = yield* Tool.Service
yield* tools.transform((editor) => {
editor.namespace({ name: "empty", description: "No tools registered yet" })
editor.add({
name: "echo",
description: "Echo text",
input: Schema.Struct({ text: Schema.String }),
output: Schema.String,
options: { pinned: true },
execute: ({ text }) => Effect.succeed({ output: text }),
})
})
const snapshot = yield* tools.snapshot()
expect(snapshot.definitions.some((tool) => tool.name === "execute")).toBe(true)
expect(snapshot.codeModeCatalog).toStrictEqual({
tools: [
{
type: "tool",
name: "echo",
description: "Echo text",
signature: "tools.echo({\n text: string,\n}): Promise<string>",
pinned: true,
},
{
type: "namespace",
name: "empty",
description: "No tools registered yet",
tools: [],
},
],
})
}).pipe(Effect.scoped, Effect.provide(toolNode)),
)
it.effect("renders Effect numbers in built-in tool signatures as number", () =>
Effect.gen(function* () {
const tools = yield* Tool.Service
const ref = Schema.String.annotate({ identifier: "Ref", description: "Element ref" })
const sentinel = Schema.Literals(["NaN", "Infinity", "-Infinity"])
const Measure = {
name: "measure",
description: "Measure",
input: Schema.Struct({ ref, amount: Schema.Number, sentinel }),
output: Schema.Struct({ exit: Schema.optionalKey(Schema.Number), sentinel }),
execute: () => Effect.succeed({ output: { exit: 0, sentinel: "NaN" as const } }),
} satisfies Info<any, any>
yield* tools.transform((editor) => {
editor.add(Measure)
editor.add({ ...Measure, name: "direct", options: { codemode: false } })
})
const snapshot = yield* tools.snapshot()
expect(snapshot.codeModeCatalog?.tools).toEqual([
{
type: "tool",
name: "measure",
description: "Measure",
signature: [
"tools.measure({",
" /** Element ref */",
" ref: string,",
" amount: number,",
' sentinel: "NaN" | "Infinity" | "-Infinity",',
"}): Promise<{",
" exit?: number,",
' sentinel: "NaN" | "Infinity" | "-Infinity",',
"}>",
].join("\n"),
pinned: false,
},
])
// Provider definitions keep Effect's plain JSON Schema encoding.
const direct = JSON.stringify(snapshot.definitions.find((tool) => tool.name === "direct"))
expect(direct).toContain('{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}')
expect(direct).not.toContain("$ref")
}).pipe(Effect.scoped, Effect.provide(toolNode)),
)
})