2026-07-21 19:46:18 +00:00
import { describe , expect } from "bun:test"
2026-07-24 15:20:26 +00:00
import { CodeModeCatalog } from "@opencode-ai/core/codemode/catalog"
2026-07-21 19:46:18 +00:00
import { CodeModeInstructions } from "@opencode-ai/core/codemode/instructions"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
2026-07-28 00:48:29 +00:00
import { Location } from "@opencode-ai/core/location"
import { AbsolutePath } from "@opencode-ai/core/schema"
2026-07-27 00:08:55 +00:00
import { Tool } from "@opencode-ai/core/tool"
2026-07-24 02:54:22 +00:00
import { Effect , Schema } from "effect"
2026-07-21 19:46:18 +00:00
import { it } from "../lib/effect"
import { readInitial , readUpdate } from "../lib/instructions"
2026-07-24 15:20:26 +00:00
const echo : CodeModeCatalog.Entry = {
path : "notes.echo" ,
description : "Echo text" ,
signature : "tools.notes.echo(input: {\n text: string,\n}): Promise<string>" ,
}
const lookup : CodeModeCatalog.Entry = {
path : "orders.lookup" ,
description : "Look up an order" ,
signature : "tools.orders.lookup(input: {\n id: string,\n}): Promise<unknown>" ,
}
2026-07-21 19:46:18 +00:00
describe ( "CodeModeInstructions" , ( ) = > {
2026-07-25 18:01:05 +00:00
it . effect ( "instructs the model not to call execute while the catalog is empty" , ( ) = >
Effect . gen ( function * ( ) {
const initialized = yield * readInitial ( CodeModeInstructions . make ( [ ] ) )
expect ( initialized . text ) . toBe (
2026-07-25 18:20:16 +00:00
"No Code Mode tools are currently available. Later Code Mode catalog updates may add or remove tools. Do not call `execute` unless there is at least one available Code Mode tool." ,
2026-07-25 18:01:05 +00:00
)
const added = yield * readUpdate ( CodeModeInstructions . make ( [ echo ] ) , initialized )
expect ( added . text ) . toContain ( "New tools are available in addition to those previously listed:" )
expect ( added . text ) . toContain ( echo . signature )
expect ( yield * readUpdate ( CodeModeInstructions . make ( [ ] ) , { values : added.values } ) ) . toMatchObject ( {
text :
"The Code Mode tool catalog has changed. This catalog supersedes the previous Code Mode tool catalog.\n\n" +
2026-07-25 18:20:16 +00:00
"No Code Mode tools are currently available. Later Code Mode catalog updates may add or remove tools. Do not call `execute` unless there is at least one available Code Mode tool." ,
2026-07-25 18:01:05 +00:00
} )
} ) ,
)
2026-07-24 15:20:26 +00:00
it . effect ( "renders the initial catalog, semantic deltas, and removal" , ( ) = >
Effect . gen ( function * ( ) {
const initialized = yield * readInitial ( CodeModeInstructions . make ( [ echo ] ) )
expect ( initialized . text ) . toContain ( "## Available tools" )
expect ( initialized . text ) . not . toContain ( "## Search" )
expect ( initialized . text ) . toContain ( ` - ${ echo . signature } // Echo text ` )
const added = yield * readUpdate ( CodeModeInstructions . make ( [ echo , lookup ] ) , initialized )
expect ( added . text ) . toContain ( "The Code Mode tool catalog has changed." )
expect ( added . text ) . toContain ( "New tools are available in addition to those previously listed:" )
expect ( added . text ) . toContain ( ` - ${ lookup . signature } // Look up an order ` )
expect ( added . text ) . not . toContain ( "## Available tools" )
const removed = yield * readUpdate ( CodeModeInstructions . make ( [ echo ] ) , { values : added.values } )
expect ( removed . text ) . toBe (
"The Code Mode tool catalog has changed.\n\n" +
"The following tools are no longer available and must not be called: tools.orders.lookup." ,
)
expect ( yield * readUpdate ( CodeModeInstructions . make ( ) , initialized ) ) . toMatchObject ( {
text : "Code Mode tools are no longer available. Do not use any previously listed Code Mode tools." ,
} )
} ) ,
)
it . effect ( "stores a canonical sorted snapshot so registration order does not churn history" , ( ) = > {
2026-07-27 00:08:55 +00:00
const alpha = ( {
name : "alpha" ,
2026-07-24 00:56:18 +00:00
description : "Alpha tool" ,
input : Schema.Struct ( { } ) ,
output : Schema.String ,
execute : ( ) = > Effect . succeed ( { output : "alpha" } ) ,
} )
2026-07-27 00:08:55 +00:00
const zeta = ( {
name : "zeta" ,
2026-07-24 00:56:18 +00:00
description : "Zeta tool" ,
input : Schema.Struct ( { } ) ,
output : Schema.String ,
execute : ( ) = > Effect . succeed ( { output : "zeta" } ) ,
} )
2026-07-28 00:48:29 +00:00
const layer = AppNodeBuilder . build ( Tool . node , [
[ Location . node , Location . boundNode ( { directory : AbsolutePath.make ( "/project" ) } ) ] ,
] )
2026-07-24 00:56:18 +00:00
return Effect . gen ( function * ( ) {
2026-07-27 00:08:55 +00:00
const tools = yield * Tool . Service
2026-07-24 00:56:18 +00:00
const initialized = yield * Effect . scoped (
Effect . gen ( function * ( ) {
2026-07-27 00:08:55 +00:00
yield * tools . transform ( ( draft ) = > {
draft . add ( { . . . zeta , options : { namespace : "tools" } } )
draft . add ( { . . . alpha , options : { namespace : "tools" } } )
} )
return yield * readInitial (
CodeModeInstructions . make ( ( yield * tools . snapshot ( ) ) . codeModeCatalog ) ,
)
2026-07-24 00:56:18 +00:00
} ) ,
)
const reordered = yield * Effect . scoped (
Effect . gen ( function * ( ) {
2026-07-27 00:08:55 +00:00
yield * tools . transform ( ( draft ) = > {
draft . add ( { . . . alpha , options : { namespace : "tools" } } )
draft . add ( { . . . zeta , options : { namespace : "tools" } } )
} )
return yield * readUpdate (
CodeModeInstructions . make ( ( yield * tools . snapshot ( ) ) . codeModeCatalog ) ,
initialized ,
)
2026-07-24 00:56:18 +00:00
} ) ,
)
expect ( reordered . changed ) . toBe ( false )
expect ( reordered . text ) . toBe ( "" )
2026-07-24 15:20:26 +00:00
} ) . pipe ( Effect . provide ( layer ) )
2026-07-21 19:46:18 +00:00
} )
} )