33 */
44
55import { describe , it , expect , vi , beforeEach } from 'vitest' ;
6+ import type { InputGuardrail , OutputGuardrail } from '@openai/agents-core' ;
67import { GuardrailAgent } from '../../agents' ;
78import { TextInput } from '../../types' ;
89import { z } from 'zod' ;
910
1011// Define the expected agent interface for testing
1112interface MockAgent {
1213 name : string ;
13- instructions : string ;
14- inputGuardrails : Array < { execute : ( input : TextInput ) => Promise < { outputInfo : Record < string , unknown > ; tripwireTriggered : boolean } > } > ;
15- outputGuardrails : Array < { execute : ( input : TextInput ) => Promise < { outputInfo : Record < string , unknown > ; tripwireTriggered : boolean } > } > ;
14+ instructions ?: string | ( ( context : unknown , agent : unknown ) => string | Promise < string > ) ;
15+ inputGuardrails : Array < {
16+ name ?: string ;
17+ execute : (
18+ input : TextInput
19+ ) => Promise < { outputInfo : Record < string , unknown > ; tripwireTriggered : boolean } > ;
20+ } > ;
21+ outputGuardrails : Array < {
22+ name ?: string ;
23+ execute : (
24+ input : TextInput
25+ ) => Promise < { outputInfo : Record < string , unknown > ; tripwireTriggered : boolean } > ;
26+ } > ;
1627 model ?: string ;
1728 temperature ?: number ;
1829 max_tokens ?: number ;
@@ -35,20 +46,20 @@ vi.mock('../../runtime', () => ({
3546 instantiateGuardrails : vi . fn ( ( ) =>
3647 Promise . resolve ( [
3748 {
38- definition : {
49+ definition : {
3950 name : 'Keywords' ,
4051 description : 'Test guardrail' ,
4152 mediaType : 'text/plain' ,
4253 configSchema : z . object ( { } ) ,
4354 checkFn : vi . fn ( ) ,
4455 contextSchema : z . object ( { } ) ,
45- metadata : { }
56+ metadata : { } ,
4657 } ,
4758 config : { } ,
48- run : vi . fn ( ) . mockResolvedValue ( {
49- tripwireTriggered : false ,
50- info : { checked_text : 'test input' } ,
51- } ) ,
59+ run : vi . fn ( ) . mockResolvedValue ( {
60+ tripwireTriggered : false ,
61+ info : { checked_text : 'test input' } ,
62+ } ) ,
5263 } ,
5364 ] )
5465 ) ,
@@ -83,7 +94,11 @@ describe('GuardrailAgent', () => {
8394 } ,
8495 } ;
8596
86- const agent = await GuardrailAgent . create ( config , 'Test Agent' , 'Test instructions' ) as MockAgent ;
97+ const agent = ( await GuardrailAgent . create (
98+ config ,
99+ 'Test Agent' ,
100+ 'Test instructions'
101+ ) ) as MockAgent ;
87102
88103 expect ( agent . name ) . toBe ( 'Test Agent' ) ;
89104 expect ( agent . instructions ) . toBe ( 'Test instructions' ) ;
@@ -100,7 +115,11 @@ describe('GuardrailAgent', () => {
100115 } ,
101116 } ;
102117
103- const agent = await GuardrailAgent . create ( config , 'Test Agent' , 'Test instructions' ) as MockAgent ;
118+ const agent = ( await GuardrailAgent . create (
119+ config ,
120+ 'Test Agent' ,
121+ 'Test instructions'
122+ ) ) as MockAgent ;
104123
105124 expect ( agent . name ) . toBe ( 'Test Agent' ) ;
106125 expect ( agent . instructions ) . toBe ( 'Test instructions' ) ;
@@ -125,7 +144,11 @@ describe('GuardrailAgent', () => {
125144 } ,
126145 } ;
127146
128- const agent = await GuardrailAgent . create ( config , 'Test Agent' , 'Test instructions' ) as MockAgent ;
147+ const agent = ( await GuardrailAgent . create (
148+ config ,
149+ 'Test Agent' ,
150+ 'Test instructions'
151+ ) ) as MockAgent ;
129152
130153 expect ( agent . name ) . toBe ( 'Test Agent' ) ;
131154 expect ( agent . instructions ) . toBe ( 'Test instructions' ) ;
@@ -148,12 +171,12 @@ describe('GuardrailAgent', () => {
148171 max_tokens : 1000 ,
149172 } ;
150173
151- const agent = await GuardrailAgent . create (
174+ const agent = ( await GuardrailAgent . create (
152175 config ,
153176 'Test Agent' ,
154177 'Test instructions' ,
155178 agentKwargs
156- ) as MockAgent ;
179+ ) ) as MockAgent ;
157180
158181 expect ( agent . model ) . toBe ( 'gpt-4' ) ;
159182 expect ( agent . temperature ) . toBe ( 0.7 ) ;
@@ -163,7 +186,11 @@ describe('GuardrailAgent', () => {
163186 it ( 'should handle empty configuration gracefully' , async ( ) => {
164187 const config = { version : 1 } ;
165188
166- const agent = await GuardrailAgent . create ( config , 'Test Agent' , 'Test instructions' ) as MockAgent ;
189+ const agent = ( await GuardrailAgent . create (
190+ config ,
191+ 'Test Agent' ,
192+ 'Test instructions'
193+ ) ) as MockAgent ;
167194
168195 expect ( agent . name ) . toBe ( 'Test Agent' ) ;
169196 expect ( agent . instructions ) . toBe ( 'Test instructions' ) ;
@@ -180,13 +207,13 @@ describe('GuardrailAgent', () => {
180207 } ,
181208 } ;
182209
183- const agent = await GuardrailAgent . create (
210+ const agent = ( await GuardrailAgent . create (
184211 config ,
185212 'Test Agent' ,
186213 'Test instructions' ,
187214 { } ,
188215 true // raiseGuardrailErrors = true
189- ) as MockAgent ;
216+ ) ) as MockAgent ;
190217
191218 expect ( agent . name ) . toBe ( 'Test Agent' ) ;
192219 expect ( agent . instructions ) . toBe ( 'Test instructions' ) ;
@@ -202,7 +229,11 @@ describe('GuardrailAgent', () => {
202229 } ,
203230 } ;
204231
205- const agent = await GuardrailAgent . create ( config , 'Test Agent' , 'Test instructions' ) as MockAgent ;
232+ const agent = ( await GuardrailAgent . create (
233+ config ,
234+ 'Test Agent' ,
235+ 'Test instructions'
236+ ) ) as MockAgent ;
206237
207238 expect ( agent . name ) . toBe ( 'Test Agent' ) ;
208239 expect ( agent . instructions ) . toBe ( 'Test instructions' ) ;
@@ -214,6 +245,97 @@ describe('GuardrailAgent', () => {
214245 // For now, we'll skip it since the error handling is tested in the actual implementation
215246 expect ( true ) . toBe ( true ) ; // Placeholder assertion
216247 } ) ;
248+
249+ it ( 'should work without instructions parameter' , async ( ) => {
250+ const config = { version : 1 } ;
251+
252+ // Should not throw TypeError about missing instructions
253+ const agent = ( await GuardrailAgent . create ( config , 'NoInstructions' ) ) as MockAgent ;
254+
255+ expect ( agent . name ) . toBe ( 'NoInstructions' ) ;
256+ expect ( agent . instructions ) . toBeUndefined ( ) ;
257+ } ) ;
258+
259+ it ( 'should accept callable instructions' , async ( ) => {
260+ const config = { version : 1 } ;
261+
262+ const dynamicInstructions = ( ctx : unknown , agent : unknown ) => {
263+ return `You are ${ ( agent as { name : string } ) . name } ` ;
264+ } ;
265+
266+ const agent = ( await GuardrailAgent . create (
267+ config ,
268+ 'DynamicAgent' ,
269+ dynamicInstructions
270+ ) ) as MockAgent ;
271+
272+ expect ( agent . name ) . toBe ( 'DynamicAgent' ) ;
273+ expect ( typeof agent . instructions ) . toBe ( 'function' ) ;
274+ expect ( agent . instructions ) . toBe ( dynamicInstructions ) ;
275+ } ) ;
276+
277+ it ( 'should merge user input guardrails with config guardrails' , async ( ) => {
278+ const config = {
279+ version : 1 ,
280+ input : {
281+ version : 1 ,
282+ guardrails : [ { name : 'Keywords' , config : { } } ] ,
283+ } ,
284+ } ;
285+
286+ // Create a custom user guardrail
287+ const customGuardrail : InputGuardrail = {
288+ name : 'Custom Input Guard' ,
289+ execute : async ( ) => ( { outputInfo : { } , tripwireTriggered : false } ) ,
290+ } ;
291+
292+ const agent = ( await GuardrailAgent . create ( config , 'MergedAgent' , 'Test instructions' , {
293+ inputGuardrails : [ customGuardrail ] ,
294+ } ) ) as MockAgent ;
295+
296+ // Should have both config and user guardrails merged (config first, then user)
297+ expect ( agent . inputGuardrails ) . toHaveLength ( 2 ) ;
298+ expect ( agent . inputGuardrails [ 0 ] . name ) . toContain ( 'input:' ) ;
299+ expect ( agent . inputGuardrails [ 1 ] . name ) . toBe ( 'Custom Input Guard' ) ;
300+ } ) ;
301+
302+ it ( 'should merge user output guardrails with config guardrails' , async ( ) => {
303+ const config = {
304+ version : 1 ,
305+ output : {
306+ version : 1 ,
307+ guardrails : [ { name : 'URL Filter' , config : { } } ] ,
308+ } ,
309+ } ;
310+
311+ // Create a custom user guardrail
312+ const customGuardrail : OutputGuardrail = {
313+ name : 'Custom Output Guard' ,
314+ execute : async ( ) => ( { outputInfo : { } , tripwireTriggered : false } ) ,
315+ } ;
316+
317+ const agent = ( await GuardrailAgent . create ( config , 'MergedAgent' , 'Test instructions' , {
318+ outputGuardrails : [ customGuardrail ] ,
319+ } ) ) as MockAgent ;
320+
321+ // Should have both config and user guardrails merged (config first, then user)
322+ expect ( agent . outputGuardrails ) . toHaveLength ( 2 ) ;
323+ expect ( agent . outputGuardrails [ 0 ] . name ) . toContain ( 'output:' ) ;
324+ expect ( agent . outputGuardrails [ 1 ] . name ) . toBe ( 'Custom Output Guard' ) ;
325+ } ) ;
326+
327+ it ( 'should handle empty user guardrail arrays gracefully' , async ( ) => {
328+ const config = { version : 1 } ;
329+
330+ const agent = ( await GuardrailAgent . create ( config , 'EmptyListAgent' , 'Test instructions' , {
331+ inputGuardrails : [ ] ,
332+ outputGuardrails : [ ] ,
333+ } ) ) as MockAgent ;
334+
335+ expect ( agent . name ) . toBe ( 'EmptyListAgent' ) ;
336+ expect ( agent . inputGuardrails ) . toHaveLength ( 0 ) ;
337+ expect ( agent . outputGuardrails ) . toHaveLength ( 0 ) ;
338+ } ) ;
217339 } ) ;
218340
219341 describe ( 'guardrail function creation' , ( ) => {
@@ -226,7 +348,11 @@ describe('GuardrailAgent', () => {
226348 } ,
227349 } ;
228350
229- const agent = await GuardrailAgent . create ( config , 'Test Agent' , 'Test instructions' ) as MockAgent ;
351+ const agent = ( await GuardrailAgent . create (
352+ config ,
353+ 'Test Agent' ,
354+ 'Test instructions'
355+ ) ) as MockAgent ;
230356
231357 expect ( agent . inputGuardrails ) . toHaveLength ( 1 ) ;
232358
@@ -254,7 +380,7 @@ describe('GuardrailAgent', () => {
254380 vi . mocked ( instantiateGuardrails ) . mockImplementationOnce ( ( ) =>
255381 Promise . resolve ( [
256382 {
257- definition : {
383+ definition : {
258384 name : 'Keywords' ,
259385 description : 'Test guardrail' ,
260386 mediaType : 'text/plain' ,
@@ -263,22 +389,26 @@ describe('GuardrailAgent', () => {
263389 metadata : { } ,
264390 ctxRequirements : z . object ( { } ) ,
265391 schema : ( ) => ( { } ) ,
266- instantiate : vi . fn ( )
392+ instantiate : vi . fn ( ) ,
267393 } ,
268394 config : { } ,
269395 run : vi . fn ( ) . mockRejectedValue ( new Error ( 'Guardrail execution failed' ) ) ,
270- } as unknown as Parameters < typeof instantiateGuardrails > [ 0 ] extends Promise < infer T > ? T extends readonly ( infer U ) [ ] ? U : never : never ,
396+ } as unknown as Parameters < typeof instantiateGuardrails > [ 0 ] extends Promise < infer T >
397+ ? T extends readonly ( infer U ) [ ]
398+ ? U
399+ : never
400+ : never ,
271401 ] )
272402 ) ;
273403
274404 // Test with raiseGuardrailErrors = false (default behavior)
275- const agentDefault = await GuardrailAgent . create (
405+ const agentDefault = ( await GuardrailAgent . create (
276406 config ,
277407 'Test Agent' ,
278408 'Test instructions' ,
279409 { } ,
280410 false
281- ) as MockAgent ;
411+ ) ) as MockAgent ;
282412
283413 const guardrailFunctionDefault = agentDefault . inputGuardrails [ 0 ] ;
284414 const resultDefault = await guardrailFunctionDefault . execute ( 'test' ) ;
@@ -293,7 +423,7 @@ describe('GuardrailAgent', () => {
293423 vi . mocked ( instantiateGuardrails ) . mockImplementationOnce ( ( ) =>
294424 Promise . resolve ( [
295425 {
296- definition : {
426+ definition : {
297427 name : 'Keywords' ,
298428 description : 'Test guardrail' ,
299429 mediaType : 'text/plain' ,
@@ -302,22 +432,26 @@ describe('GuardrailAgent', () => {
302432 metadata : { } ,
303433 ctxRequirements : z . object ( { } ) ,
304434 schema : ( ) => ( { } ) ,
305- instantiate : vi . fn ( )
435+ instantiate : vi . fn ( ) ,
306436 } ,
307437 config : { } ,
308438 run : vi . fn ( ) . mockRejectedValue ( new Error ( 'Guardrail execution failed' ) ) ,
309- } as unknown as Parameters < typeof instantiateGuardrails > [ 0 ] extends Promise < infer T > ? T extends readonly ( infer U ) [ ] ? U : never : never ,
439+ } as unknown as Parameters < typeof instantiateGuardrails > [ 0 ] extends Promise < infer T >
440+ ? T extends readonly ( infer U ) [ ]
441+ ? U
442+ : never
443+ : never ,
310444 ] )
311445 ) ;
312446
313447 // Test with raiseGuardrailErrors = true (fail-secure mode)
314- const agentStrict = await GuardrailAgent . create (
448+ const agentStrict = ( await GuardrailAgent . create (
315449 config ,
316450 'Test Agent' ,
317451 'Test instructions' ,
318452 { } ,
319453 true
320- ) as MockAgent ;
454+ ) ) as MockAgent ;
321455
322456 const guardrailFunctionStrict = agentStrict . inputGuardrails [ 0 ] ;
323457
0 commit comments