agentshield-sdk 13.5.0 → 14.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,463 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Agent Shield — Framework Integration Wrappers
5
+ *
6
+ * Plug-and-play integrations for next-generation AI agent frameworks:
7
+ * - CrewAI (task decorators & callbacks)
8
+ * - Google Agent Development Kit (plugin system)
9
+ * - Microsoft Agent Framework (middleware pipeline)
10
+ *
11
+ * These close gaps identified in the Microsoft Agent Governance Toolkit
12
+ * parity audit.
13
+ *
14
+ * @module integrations-frameworks
15
+ */
16
+
17
+ const { AgentShield } = require('./index');
18
+ const { ShieldBlockError } = require('./integrations');
19
+
20
+ // =========================================================================
21
+ // CrewAI Integration
22
+ // =========================================================================
23
+
24
+ /**
25
+ * Creates Agent Shield callbacks for CrewAI task lifecycle.
26
+ *
27
+ * CrewAI uses task decorators and callbacks. This wrapper provides
28
+ * beforeTask / afterTask hooks that scan task descriptions, expected
29
+ * outputs, and task results for prompt injection and other threats.
30
+ *
31
+ * Usage:
32
+ * const { shieldCrewAI } = require('agentshield-sdk/src/integrations-frameworks');
33
+ * const { beforeTask, afterTask } = shieldCrewAI({ blockOnThreat: true });
34
+ *
35
+ * // In your CrewAI task lifecycle:
36
+ * beforeTask(task, agent); // throws ShieldBlockError if threat found
37
+ * const output = await task.run();
38
+ * afterTask(task, output); // throws ShieldBlockError if threat found
39
+ *
40
+ * @param {object} [options]
41
+ * @param {string} [options.sensitivity='high'] - Detection sensitivity level.
42
+ * @param {boolean} [options.blockOnThreat=true] - Whether to throw on threat detection.
43
+ * @param {string} [options.blockThreshold='high'] - Minimum severity that triggers a block.
44
+ * @param {function} [options.onThreat] - Callback when a threat is detected.
45
+ * @returns {{ beforeTask: function, afterTask: function, shield: AgentShield }}
46
+ */
47
+ function shieldCrewAI(options = {}) {
48
+ const shield = new AgentShield({
49
+ sensitivity: options.sensitivity || 'high',
50
+ blockOnThreat: options.blockOnThreat !== false,
51
+ blockThreshold: options.blockThreshold || 'high'
52
+ });
53
+ const onThreat = options.onThreat || null;
54
+
55
+ /**
56
+ * Scans a CrewAI task before execution.
57
+ * Inspects task.description and task.expected_output for injection.
58
+ *
59
+ * @param {object} task - CrewAI task object.
60
+ * @param {object} [agent] - CrewAI agent assigned to the task.
61
+ * @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
62
+ */
63
+ function beforeTask(task, agent) {
64
+ if (!task) return;
65
+
66
+ const fields = [];
67
+ if (task.description) fields.push(String(task.description));
68
+ if (task.expected_output) fields.push(String(task.expected_output));
69
+
70
+ for (const text of fields) {
71
+ const result = shield.scanInput(text);
72
+ if (result.threats && result.threats.length > 0) {
73
+ if (onThreat) {
74
+ try {
75
+ onThreat({
76
+ phase: 'before_task',
77
+ threats: result.threats,
78
+ task: task.description || '',
79
+ agent: agent && agent.role ? agent.role : undefined
80
+ });
81
+ } catch (e) {
82
+ console.error('[Agent Shield] onThreat callback error:', e.message);
83
+ }
84
+ }
85
+ if (result.blocked) {
86
+ throw new ShieldBlockError('CrewAI task blocked by Agent Shield', result.threats);
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Scans a CrewAI task output after execution.
94
+ *
95
+ * @param {object} task - CrewAI task object.
96
+ * @param {*} output - Task execution output.
97
+ * @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
98
+ */
99
+ function afterTask(task, output) {
100
+ if (output == null) return;
101
+
102
+ const text = typeof output === 'string' ? output : JSON.stringify(output);
103
+ const result = shield.scanOutput(text);
104
+
105
+ if (result.threats && result.threats.length > 0) {
106
+ if (onThreat) {
107
+ try {
108
+ onThreat({
109
+ phase: 'after_task',
110
+ threats: result.threats,
111
+ task: task && task.description ? task.description : ''
112
+ });
113
+ } catch (e) {
114
+ console.error('[Agent Shield] onThreat callback error:', e.message);
115
+ }
116
+ }
117
+ if (result.blocked) {
118
+ throw new ShieldBlockError('CrewAI task output blocked by Agent Shield', result.threats);
119
+ }
120
+ }
121
+ }
122
+
123
+ return { beforeTask, afterTask, shield };
124
+ }
125
+
126
+ // =========================================================================
127
+ // Google Agent Development Kit (ADK) Integration
128
+ // =========================================================================
129
+
130
+ /**
131
+ * Creates Agent Shield hooks for the Google Agent Development Kit plugin system.
132
+ *
133
+ * Google ADK uses a plugin architecture with lifecycle hooks. This wrapper
134
+ * provides beforeToolCall, afterToolCall, and beforeGenerate functions that
135
+ * scan tool arguments, tool results, and generation prompts for threats.
136
+ *
137
+ * Usage:
138
+ * const { shieldGoogleADK } = require('agentshield-sdk/src/integrations-frameworks');
139
+ * const hooks = shieldGoogleADK({ blockOnThreat: true });
140
+ *
141
+ * // Register as ADK plugin callbacks:
142
+ * hooks.beforeToolCall('web_search', { query: userInput });
143
+ * const result = await tool.execute(args);
144
+ * hooks.afterToolCall('web_search', result);
145
+ * hooks.beforeGenerate(prompt);
146
+ *
147
+ * @param {object} [options]
148
+ * @param {string} [options.sensitivity='high'] - Detection sensitivity level.
149
+ * @param {boolean} [options.blockOnThreat=true] - Whether to throw on threat detection.
150
+ * @param {string} [options.blockThreshold='high'] - Minimum severity that triggers a block.
151
+ * @param {function} [options.onThreat] - Callback when a threat is detected.
152
+ * @returns {{ beforeToolCall: function, afterToolCall: function, beforeGenerate: function, shield: AgentShield }}
153
+ */
154
+ function shieldGoogleADK(options = {}) {
155
+ const shield = new AgentShield({
156
+ sensitivity: options.sensitivity || 'high',
157
+ blockOnThreat: options.blockOnThreat !== false,
158
+ blockThreshold: options.blockThreshold || 'high'
159
+ });
160
+ const onThreat = options.onThreat || null;
161
+
162
+ /**
163
+ * Scans tool arguments before a tool call.
164
+ *
165
+ * @param {string} toolName - Name of the tool being called.
166
+ * @param {*} args - Tool arguments (object, string, or any serializable value).
167
+ * @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
168
+ */
169
+ function beforeToolCall(toolName, args) {
170
+ if (args == null) return;
171
+
172
+ const text = typeof args === 'string' ? args : JSON.stringify(args);
173
+ const result = shield.scanInput(text);
174
+
175
+ if (result.threats && result.threats.length > 0) {
176
+ if (onThreat) {
177
+ try {
178
+ onThreat({
179
+ phase: 'before_tool_call',
180
+ toolName: toolName || 'unknown',
181
+ threats: result.threats
182
+ });
183
+ } catch (e) {
184
+ console.error('[Agent Shield] onThreat callback error:', e.message);
185
+ }
186
+ }
187
+ if (result.blocked) {
188
+ throw new ShieldBlockError(
189
+ `Google ADK tool "${toolName || 'unknown'}" call blocked by Agent Shield`,
190
+ result.threats
191
+ );
192
+ }
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Scans tool results after a tool call.
198
+ *
199
+ * @param {string} toolName - Name of the tool that was called.
200
+ * @param {*} result - Tool execution result.
201
+ * @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
202
+ */
203
+ function afterToolCall(toolName, toolResult) {
204
+ if (toolResult == null) return;
205
+
206
+ const text = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult);
207
+ const result = shield.scanOutput(text);
208
+
209
+ if (result.threats && result.threats.length > 0) {
210
+ if (onThreat) {
211
+ try {
212
+ onThreat({
213
+ phase: 'after_tool_call',
214
+ toolName: toolName || 'unknown',
215
+ threats: result.threats
216
+ });
217
+ } catch (e) {
218
+ console.error('[Agent Shield] onThreat callback error:', e.message);
219
+ }
220
+ }
221
+ if (result.blocked) {
222
+ throw new ShieldBlockError(
223
+ `Google ADK tool "${toolName || 'unknown'}" result blocked by Agent Shield`,
224
+ result.threats
225
+ );
226
+ }
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Scans a prompt before generation.
232
+ *
233
+ * @param {string|*} prompt - The prompt to scan.
234
+ * @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
235
+ */
236
+ function beforeGenerate(prompt) {
237
+ if (prompt == null) return;
238
+
239
+ const text = typeof prompt === 'string' ? prompt : JSON.stringify(prompt);
240
+ const result = shield.scanInput(text);
241
+
242
+ if (result.threats && result.threats.length > 0) {
243
+ if (onThreat) {
244
+ try {
245
+ onThreat({
246
+ phase: 'before_generate',
247
+ threats: result.threats
248
+ });
249
+ } catch (e) {
250
+ console.error('[Agent Shield] onThreat callback error:', e.message);
251
+ }
252
+ }
253
+ if (result.blocked) {
254
+ throw new ShieldBlockError('Google ADK generation blocked by Agent Shield', result.threats);
255
+ }
256
+ }
257
+ }
258
+
259
+ return { beforeToolCall, afterToolCall, beforeGenerate, shield };
260
+ }
261
+
262
+ // =========================================================================
263
+ // Microsoft Agent Framework Integration
264
+ // =========================================================================
265
+
266
+ /**
267
+ * Creates Agent Shield middleware for the Microsoft Agent Framework pipeline.
268
+ *
269
+ * The MS Agent Framework uses a middleware pattern where each middleware
270
+ * receives a context and a next() function. This wrapper scans context.input
271
+ * before calling next(), then scans context.output after next() returns.
272
+ *
273
+ * Usage:
274
+ * const { shieldMSAgentFramework } = require('agentshield-sdk/src/integrations-frameworks');
275
+ * const { agentMiddleware } = shieldMSAgentFramework({ blockOnThreat: true });
276
+ *
277
+ * // Register in the MS Agent Framework pipeline:
278
+ * agent.use(agentMiddleware);
279
+ *
280
+ * @param {object} [options]
281
+ * @param {string} [options.sensitivity='high'] - Detection sensitivity level.
282
+ * @param {boolean} [options.blockOnThreat=true] - Whether to throw on threat detection.
283
+ * @param {string} [options.blockThreshold='high'] - Minimum severity that triggers a block.
284
+ * @param {function} [options.onThreat] - Callback when a threat is detected.
285
+ * @returns {{ agentMiddleware: function, shield: AgentShield }}
286
+ */
287
+ function shieldMSAgentFramework(options = {}) {
288
+ const shield = new AgentShield({
289
+ sensitivity: options.sensitivity || 'high',
290
+ blockOnThreat: options.blockOnThreat !== false,
291
+ blockThreshold: options.blockThreshold || 'high'
292
+ });
293
+ const onThreat = options.onThreat || null;
294
+
295
+ /**
296
+ * Middleware function for the MS Agent Framework pipeline.
297
+ * Scans context.input before next() and context.output after next().
298
+ *
299
+ * @param {object} context - Pipeline context with input/output properties.
300
+ * @param {function} next - Next middleware in the pipeline.
301
+ * @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
302
+ */
303
+ async function agentMiddleware(context, next) {
304
+ // Scan input before passing to next middleware
305
+ if (context && context.input != null) {
306
+ const inputText = typeof context.input === 'string'
307
+ ? context.input
308
+ : JSON.stringify(context.input);
309
+
310
+ const inputResult = shield.scanInput(inputText);
311
+
312
+ if (inputResult.threats && inputResult.threats.length > 0) {
313
+ if (onThreat) {
314
+ try {
315
+ onThreat({
316
+ phase: 'input',
317
+ threats: inputResult.threats,
318
+ text: inputText
319
+ });
320
+ } catch (e) {
321
+ console.error('[Agent Shield] onThreat callback error:', e.message);
322
+ }
323
+ }
324
+ if (inputResult.blocked) {
325
+ throw new ShieldBlockError(
326
+ 'MS Agent Framework input blocked by Agent Shield',
327
+ inputResult.threats
328
+ );
329
+ }
330
+ }
331
+ }
332
+
333
+ // Call next middleware in the pipeline
334
+ await next();
335
+
336
+ // Scan output after pipeline execution
337
+ if (context && context.output != null) {
338
+ const outputText = typeof context.output === 'string'
339
+ ? context.output
340
+ : JSON.stringify(context.output);
341
+
342
+ const outputResult = shield.scanOutput(outputText);
343
+
344
+ if (outputResult.threats && outputResult.threats.length > 0) {
345
+ if (onThreat) {
346
+ try {
347
+ onThreat({
348
+ phase: 'output',
349
+ threats: outputResult.threats,
350
+ text: outputText
351
+ });
352
+ } catch (e) {
353
+ console.error('[Agent Shield] onThreat callback error:', e.message);
354
+ }
355
+ }
356
+ if (outputResult.blocked) {
357
+ throw new ShieldBlockError(
358
+ 'MS Agent Framework output blocked by Agent Shield',
359
+ outputResult.threats
360
+ );
361
+ }
362
+ }
363
+ }
364
+ }
365
+
366
+ return { agentMiddleware, shield };
367
+ }
368
+
369
+ // =========================================================================
370
+ // Google ADK JavaScript (adk-js) Integration
371
+ // =========================================================================
372
+
373
+ /**
374
+ * Creates Agent Shield middleware for the Google ADK JavaScript/TypeScript SDK.
375
+ *
376
+ * Google ADK for JS (GA April 2026) uses a callback-based agent lifecycle.
377
+ * This wrapper returns a plugin object compatible with ADK-JS's plugin API
378
+ * that scans tool inputs, tool outputs, and generated content.
379
+ *
380
+ * Usage:
381
+ * const { shieldGoogleADKJS } = require('agentshield-sdk/src/integrations-frameworks');
382
+ * const plugin = shieldGoogleADKJS({ blockOnThreat: true });
383
+ *
384
+ * // Register with ADK-JS agent:
385
+ * const agent = new Agent({ plugins: [plugin] });
386
+ *
387
+ * @param {object} [options]
388
+ * @param {string} [options.sensitivity='high'] - Detection sensitivity level.
389
+ * @param {boolean} [options.blockOnThreat=true] - Whether to throw on threat detection.
390
+ * @param {string} [options.blockThreshold='high'] - Minimum severity that triggers a block.
391
+ * @param {function} [options.onThreat] - Callback when a threat is detected.
392
+ * @returns {{ name: string, beforeToolCall: function, afterToolCall: function, beforeModelCall: function, afterModelCall: function, shield: AgentShield }}
393
+ */
394
+ function shieldGoogleADKJS(options = {}) {
395
+ const shield = new AgentShield({
396
+ sensitivity: options.sensitivity || 'high',
397
+ blockOnThreat: options.blockOnThreat !== false,
398
+ blockThreshold: options.blockThreshold || 'high'
399
+ });
400
+ const onThreat = options.onThreat || null;
401
+
402
+ function _scan(text, phase, meta) {
403
+ if (!text) return;
404
+ const result = shield.scanInput(text);
405
+ if (result.threats && result.threats.length > 0) {
406
+ if (onThreat) {
407
+ try { onThreat({ phase, ...meta, threats: result.threats }); }
408
+ catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); }
409
+ }
410
+ if (result.blocked) {
411
+ throw new ShieldBlockError(`Google ADK-JS ${phase} blocked by Agent Shield`, result.threats);
412
+ }
413
+ }
414
+ }
415
+
416
+ return {
417
+ name: 'AgentShieldPlugin',
418
+
419
+ beforeToolCall(context) {
420
+ const text = context.args != null
421
+ ? (typeof context.args === 'string' ? context.args : JSON.stringify(context.args))
422
+ : null;
423
+ _scan(text, 'before_tool_call', { toolName: context.toolName || 'unknown' });
424
+ },
425
+
426
+ afterToolCall(context) {
427
+ const text = context.result != null
428
+ ? (typeof context.result === 'string' ? context.result : JSON.stringify(context.result))
429
+ : null;
430
+ _scan(text, 'after_tool_call', { toolName: context.toolName || 'unknown' });
431
+ },
432
+
433
+ beforeModelCall(context) {
434
+ const text = context.prompt || context.input;
435
+ if (text) _scan(typeof text === 'string' ? text : JSON.stringify(text), 'before_model_call', {});
436
+ },
437
+
438
+ afterModelCall(context) {
439
+ const text = context.response || context.output;
440
+ if (!text) return;
441
+ const outputText = typeof text === 'string' ? text : JSON.stringify(text);
442
+ const result = shield.scanOutput(outputText);
443
+ if (result.threats && result.threats.length > 0) {
444
+ if (onThreat) {
445
+ try { onThreat({ phase: 'after_model_call', threats: result.threats }); }
446
+ catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); }
447
+ }
448
+ if (result.blocked) {
449
+ throw new ShieldBlockError('Google ADK-JS model output blocked by Agent Shield', result.threats);
450
+ }
451
+ }
452
+ },
453
+
454
+ shield
455
+ };
456
+ }
457
+
458
+ module.exports = {
459
+ shieldCrewAI,
460
+ shieldGoogleADK,
461
+ shieldGoogleADKJS,
462
+ shieldMSAgentFramework
463
+ };
@@ -493,6 +493,210 @@ function shieldFetch(fetchFn, options = {}) {
493
493
  };
494
494
  }
495
495
 
496
+ // =========================================================================
497
+ // OpenAI Agents SDK (@openai/agents) — April 2026 release
498
+ // =========================================================================
499
+
500
+ /**
501
+ * Creates guardrails for the OpenAI Agents SDK (@openai/agents).
502
+ *
503
+ * The OpenAI Agents SDK (Python and TypeScript, April 2026 update) uses a
504
+ * Guardrail primitive that validates inputs and outputs. Agent Shield plugs
505
+ * in natively as both an input guardrail (scanning user messages) and an
506
+ * output guardrail (scanning agent responses).
507
+ *
508
+ * Compatible with:
509
+ * - @openai/agents (TypeScript/JavaScript)
510
+ * - openai-agents (Python — use the Python SDK's equivalent)
511
+ *
512
+ * Usage:
513
+ * const { Agent, run } = require('@openai/agents');
514
+ * const { shieldOpenAIAgent } = require('agentshield-sdk');
515
+ *
516
+ * const { inputGuardrail, outputGuardrail } = shieldOpenAIAgent({
517
+ * blockOnThreat: true,
518
+ * sensitivity: 'high'
519
+ * });
520
+ *
521
+ * const agent = new Agent({
522
+ * name: 'Assistant',
523
+ * instructions: 'You are a helpful assistant',
524
+ * inputGuardrails: [inputGuardrail],
525
+ * outputGuardrails: [outputGuardrail]
526
+ * });
527
+ *
528
+ * const result = await run(agent, userInput);
529
+ *
530
+ * @param {object} [options]
531
+ * @param {string} [options.sensitivity='high'] - Detection sensitivity.
532
+ * @param {boolean} [options.blockOnThreat=true] - Trip guardrail tripwire on threats.
533
+ * @param {string} [options.blockThreshold='high'] - Minimum severity that blocks.
534
+ * @param {boolean} [options.pii=true] - Redact PII from inputs before handing to the agent.
535
+ * @param {boolean} [options.scanToolCalls=true] - Scan arguments to tool calls.
536
+ * @param {function} [options.onThreat] - Callback when threat detected.
537
+ * @returns {{ inputGuardrail: object, outputGuardrail: object, toolGuardrail: object, shield: AgentShield }}
538
+ */
539
+ function shieldOpenAIAgent(options = {}) {
540
+ const shield = new AgentShield({
541
+ sensitivity: options.sensitivity || 'high',
542
+ blockOnThreat: options.blockOnThreat !== false,
543
+ blockThreshold: options.blockThreshold || 'high',
544
+ onThreat: options.onThreat
545
+ });
546
+
547
+ const piiRedactor = options.pii !== false ? new PIIRedactor() : null;
548
+
549
+ /**
550
+ * Input guardrail — runs on every user message before the agent sees it.
551
+ * Returns the shape expected by @openai/agents: { outputInfo, tripwireTriggered }.
552
+ */
553
+ const inputGuardrail = {
554
+ name: 'Agent Shield — Input',
555
+ execute: async (ctx) => {
556
+ // @openai/agents passes { input, context, agent }. Input may be a string
557
+ // or an array of message items. We scan every user-role text item.
558
+ const input = ctx.input || ctx.message || ctx;
559
+ const texts = normalizeAgentInput(input);
560
+
561
+ let allThreats = [];
562
+ let maxSeverity = null;
563
+
564
+ for (const text of texts) {
565
+ const result = shield.scanInput(text);
566
+ if (result.threats && result.threats.length > 0) {
567
+ allThreats = allThreats.concat(result.threats);
568
+ for (const t of result.threats) {
569
+ if (!maxSeverity || SEVERITY_RANK[t.severity] < SEVERITY_RANK[maxSeverity]) {
570
+ maxSeverity = t.severity;
571
+ }
572
+ }
573
+ }
574
+ }
575
+
576
+ const tripwireTriggered = shouldBlock(maxSeverity, options.blockThreshold || 'high');
577
+
578
+ return {
579
+ outputInfo: {
580
+ threats: allThreats,
581
+ maxSeverity,
582
+ scannedBy: 'agentshield-sdk',
583
+ piiRedacted: piiRedactor ? true : false
584
+ },
585
+ tripwireTriggered
586
+ };
587
+ }
588
+ };
589
+
590
+ /**
591
+ * Output guardrail — runs on agent responses before they reach the user.
592
+ * Catches prompt leaks, PII in output, canary tokens, etc.
593
+ */
594
+ const outputGuardrail = {
595
+ name: 'Agent Shield — Output',
596
+ execute: async (ctx) => {
597
+ const output = ctx.agentOutput || ctx.output || ctx.finalOutput || ctx;
598
+ const text = typeof output === 'string' ? output : JSON.stringify(output);
599
+
600
+ const result = shield.scanOutput(text);
601
+ const threats = result.threats || [];
602
+ const maxSeverity = threats.reduce((acc, t) => {
603
+ if (!acc || SEVERITY_RANK[t.severity] < SEVERITY_RANK[acc]) return t.severity;
604
+ return acc;
605
+ }, null);
606
+
607
+ return {
608
+ outputInfo: {
609
+ threats,
610
+ maxSeverity,
611
+ scannedBy: 'agentshield-sdk'
612
+ },
613
+ tripwireTriggered: shouldBlock(maxSeverity, options.blockThreshold || 'high')
614
+ };
615
+ }
616
+ };
617
+
618
+ /**
619
+ * Tool guardrail — runs before tool execution. Scans tool arguments for
620
+ * injection, path traversal, SSRF targets, and other tool-abuse patterns.
621
+ */
622
+ const toolGuardrail = {
623
+ name: 'Agent Shield — Tool',
624
+ execute: async (ctx) => {
625
+ const toolName = ctx.toolName || ctx.tool?.name || 'unknown';
626
+ const args = ctx.args || ctx.arguments || {};
627
+ const argsText = typeof args === 'string' ? args : JSON.stringify(args);
628
+
629
+ const result = shield.scanToolCall(toolName, typeof args === 'object' ? args : { input: args });
630
+ const threats = result.threats || [];
631
+ const maxSeverity = threats.reduce((acc, t) => {
632
+ if (!acc || SEVERITY_RANK[t.severity] < SEVERITY_RANK[acc]) return t.severity;
633
+ return acc;
634
+ }, null);
635
+
636
+ return {
637
+ outputInfo: {
638
+ threats,
639
+ toolName,
640
+ maxSeverity,
641
+ scannedBy: 'agentshield-sdk'
642
+ },
643
+ tripwireTriggered: shouldBlock(maxSeverity, options.blockThreshold || 'high')
644
+ };
645
+ }
646
+ };
647
+
648
+ return { inputGuardrail, outputGuardrail, toolGuardrail, shield };
649
+ }
650
+
651
+ /** Severity rank for block-threshold comparisons (lower number = higher severity). */
652
+ const SEVERITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
653
+
654
+ /** Returns true if maxSeverity meets or exceeds the configured threshold. */
655
+ function shouldBlock(maxSeverity, threshold) {
656
+ if (!maxSeverity) return false;
657
+ return SEVERITY_RANK[maxSeverity] <= SEVERITY_RANK[threshold];
658
+ }
659
+
660
+ /**
661
+ * Normalizes the OpenAI Agents SDK input shape into an array of user-role text strings.
662
+ * Handles: string, array of message items, message with content parts, etc.
663
+ */
664
+ function normalizeAgentInput(input) {
665
+ if (typeof input === 'string') return [input];
666
+ if (!input) return [];
667
+
668
+ // Array of messages
669
+ if (Array.isArray(input)) {
670
+ const texts = [];
671
+ for (const item of input) {
672
+ if (typeof item === 'string') texts.push(item);
673
+ else if (item?.role === 'user' || item?.role === 'system') {
674
+ if (typeof item.content === 'string') texts.push(item.content);
675
+ else if (Array.isArray(item.content)) {
676
+ for (const part of item.content) {
677
+ if (typeof part === 'string') texts.push(part);
678
+ else if (part?.type === 'text' && part.text) texts.push(part.text);
679
+ else if (part?.text) texts.push(part.text);
680
+ }
681
+ }
682
+ }
683
+ }
684
+ return texts;
685
+ }
686
+
687
+ // Single message object
688
+ if (input.content) {
689
+ if (typeof input.content === 'string') return [input.content];
690
+ if (Array.isArray(input.content)) {
691
+ return input.content
692
+ .map(p => typeof p === 'string' ? p : (p?.text || ''))
693
+ .filter(Boolean);
694
+ }
695
+ }
696
+
697
+ return [];
698
+ }
699
+
496
700
  // =========================================================================
497
701
  // Shared Error Class
498
702
  // =========================================================================
@@ -516,6 +720,9 @@ module.exports = {
516
720
  // OpenAI
517
721
  shieldOpenAIClient,
518
722
 
723
+ // OpenAI Agents SDK (@openai/agents, April 2026)
724
+ shieldOpenAIAgent,
725
+
519
726
  // Vercel AI
520
727
  shieldVercelAI,
521
728