@@ -6,6 +6,7 @@ import { Agent } from "@opencode/core/agent"
66import { Bus } from "@opencode/core/bus"
77import { Config } from "@opencode/core/config"
88import { Directory , Document , Event , Info } from "@opencode/schema/config"
9+ import { Model } from "@opencode/schema/model"
910import { ConfigAgentPlugin } from "@opencode/core/config/plugin/agent"
1011import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder"
1112import { LayerNode } from "@opencode/util/effect/layer-node"
@@ -17,7 +18,7 @@ import { AbsolutePath } from "@opencode/core/schema"
1718import { ConfigMigrateV1 } from "@opencode/core/v1/config/migrate"
1819import { ConfigAgentV1 } from "@opencode/core/v1/config/agent"
1920import { advance , drain } from "../lib/clock"
20- import { tmpdir } from "../fixture/tmpdir"
21+ import { tmpdir , tmpdirScoped } from "../fixture/tmpdir"
2122import { testEffect } from "../lib/effect"
2223import { agentHost , host } from "../plugin/host"
2324
@@ -60,6 +61,76 @@ test("keeps schema fields and name out of legacy agent options", () => {
6061} )
6162
6263describe ( "ConfigAgentPlugin.Plugin" , ( ) => {
64+ for ( const item of [
65+ { name : "separate legacy variant" , frontmatter : "model: example/chat\nvariant: high" , model : "example/chat#high" } ,
66+ { name : "unqualified model" , frontmatter : "model: example/chat" , model : "example/chat" } ,
67+ { name : "embedded native variant" , frontmatter : "model: example/chat#high" , model : "example/chat#high" } ,
68+ {
69+ name : "structured native variant" ,
70+ frontmatter : "model:\n providerID: example\n model: chat\n variant: high" ,
71+ model : "example/chat#high" ,
72+ } ,
73+ {
74+ name : "structured unqualified model" ,
75+ frontmatter : "model:\n providerID: example\n model: chat" ,
76+ model : "example/chat" ,
77+ } ,
78+ { name : "standalone variant" , frontmatter : "variant: high" , model : undefined } ,
79+ {
80+ name : "embedded native variant with an ignored separate variant" ,
81+ frontmatter : "model: example/chat#high\nvariant: low" ,
82+ model : "example/chat#high" ,
83+ } ,
84+ {
85+ name : "structured native variant with an ignored separate variant" ,
86+ frontmatter : "model:\n providerID: example\n model: chat\n variant: high\nvariant: low" ,
87+ model : "example/chat#high" ,
88+ } ,
89+ ] ) {
90+ for ( const native of [ false , true ] ) {
91+ it . live ( `loads Markdown ${ item . name } ${ native ? " with native request and permissions" : "" } ` , ( ) =>
92+ Effect . gen ( function * ( ) {
93+ const agent = yield * loadMarkdownAgent (
94+ native
95+ ? `${ item . frontmatter }
96+ request:
97+ headers:
98+ x-agent: native
99+ body:
100+ effort: high
101+ permissions:
102+ - action: edit
103+ resource: "*"
104+ effect: deny`
105+ : item . frontmatter ,
106+ )
107+ expect ( agent . model ) . toEqual ( item . model === undefined ? undefined : Model . Ref . parse ( item . model ) )
108+ expect ( agent . request ) . toEqual ( {
109+ settings : { } ,
110+ headers : native ? { "x-agent" : "native" } : { } ,
111+ body : native ? { effort : "high" } : { } ,
112+ } )
113+ if ( native ) {
114+ expect ( agent . permissions ) . toContainEqual ( { action : "edit" , resource : "*" , effect : "deny" } )
115+ expect ( Permission . evaluate ( "edit" , "example.txt" , agent . permissions ) . effect ) . toBe ( "deny" )
116+ }
117+ } ) ,
118+ )
119+ }
120+ }
121+
122+ for ( const variant of [ undefined , "high" ] ) {
123+ it . live ( `loads Markdown legacy temperature ${ variant ? "with" : "without" } a separate variant` , ( ) =>
124+ Effect . gen ( function * ( ) {
125+ const agent = yield * loadMarkdownAgent (
126+ `model: example/chat\ntemperature: 0.5${ variant ? `\nvariant: ${ variant } ` : "" } ` ,
127+ )
128+ expect ( agent . model ) . toEqual ( Model . Ref . parse ( variant ? "example/chat#high" : "example/chat" ) )
129+ expect ( agent . request ) . toEqual ( { settings : { } , headers : { } , body : { temperature : 0.5 } } )
130+ } ) ,
131+ )
132+ }
133+
63134 it . effect ( "matches POSIX paths against home-relative permissions" , ( ) =>
64135 Effect . gen ( function * ( ) {
65136 const permissions = yield * loadHomePermissions ( "/home/test" )
@@ -560,6 +631,26 @@ Use native v2 fields.`,
560631 )
561632} )
562633
634+ function loadMarkdownAgent ( frontmatter : string ) {
635+ return Effect . gen ( function * ( ) {
636+ const tmp = yield * tmpdirScoped ( )
637+ const fs = yield * FSUtil . Service
638+ yield * fs . makeDirectory ( path . join ( tmp . path , "agents" ) )
639+ yield * fs . writeFileString (
640+ path . join ( tmp . path , "agents" , "reviewer.md" ) ,
641+ `---\n${ frontmatter } \n---\nReview carefully.` ,
642+ )
643+ const agents = yield * Agent . Service
644+ yield * ConfigAgentPlugin . Plugin . effect ( host ( { agent : agentHost ( agents ) } ) ) . pipe (
645+ Effect . provide ( Config . testLayer ( [ directoryEntry ( tmp . path ) ] ) ) ,
646+ )
647+ const agent = yield * agents . get ( Agent . ID . make ( "reviewer" ) )
648+ if ( ! agent ) throw new Error ( "expected configured Markdown agent" )
649+ expect ( agent . system ) . toBe ( "Review carefully." )
650+ return agent
651+ } )
652+ }
653+
563654function directoryEntry ( directory : string ) {
564655 return new Directory ( { type : "directory" , path : AbsolutePath . make ( directory ) } )
565656}
0 commit comments