Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 70 additions & 40 deletions plugins/javelin/guardrails.ts → plugins/highflame/guardrails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import {
} from '../types';
import { getCurrentContentPart } from '../utils';

interface JavelinCredentials {
interface HighflameCredentials {
apiKey: string;
domain?: string;
application?: string;
}

interface GuardrailConfig {
name: string;
config?: Record<string, any>;
}

interface GuardrailAssessment {
[key: string]: {
categories?: Record<string, boolean>;
Expand All @@ -34,49 +39,65 @@ interface GuardrailsResponse {
assessments: Array<GuardrailAssessment>;
}

async function callJavelinGuardrails(
// Default guardrails to run if none specified
const DEFAULT_GUARDRAILS: GuardrailConfig[] = [
{ name: 'trustsafety', config: { threshold: 0.75 } },
{ name: 'promptinjectiondetection', config: { threshold: 0.8 } },
];

async function callHighflameGuardrails(
text: string,
credentials: JavelinCredentials
credentials: HighflameCredentials,
guardrails?: GuardrailConfig[],
globalThreshold?: number
): Promise<GuardrailsResponse> {
// Strip https:// or http:// from domain if present
let domain = credentials.domain || 'api-dev.javelin.live';
let domain = credentials.domain || 'api-dev.highflame.dev';
domain = domain.replace(/^https?:\/\//, '');

const apiUrl = `https://${domain}/v1/guardrails/apply`;

console.log('[Javelin] Calling API:', apiUrl);
console.log('[Javelin] Application:', credentials.application);
console.log('[Highflame] Calling API:', apiUrl);
console.log('[Highflame] Application:', credentials.application);

const headers: Record<string, string> = {
'Content-Type': 'application/json',
'x-javelin-apikey': credentials.apiKey,
'x-highflame-apikey': credentials.apiKey,
};

if (credentials.application) {
headers['x-javelin-application'] = credentials.application;
headers['x-highflame-application'] = credentials.application;
}

const requestBody = {
// Use provided guardrails or defaults
const guardrailsToRun =
guardrails && guardrails.length > 0 ? guardrails : DEFAULT_GUARDRAILS;

const requestBody: Record<string, any> = {
input: { text },
config: {},
metadata: {},
guardrails: guardrailsToRun,
};

console.log('[Javelin] Request body:', JSON.stringify(requestBody));
// Add global config if threshold specified
if (globalThreshold !== undefined) {
requestBody.config = { threshold: globalThreshold };
}

console.log('[Highflame] Request body:', JSON.stringify(requestBody));

const response = await fetch(apiUrl, {
method: 'POST',
headers,
body: JSON.stringify(requestBody),
});

console.log('[Javelin] Response status:', response.status);
console.log('[Highflame] Response status:', response.status);

if (!response.ok) {
const errorText = await response.text();
console.error('[Javelin] API error:', errorText);
console.error('[Highflame] API error:', errorText);
throw new Error(
`Javelin Guardrails API error: ${response.status} ${response.statusText} - ${errorText}`
`Highflame Guardrails API error: ${response.status} ${response.statusText} - ${errorText}`
);
}

Expand All @@ -90,28 +111,28 @@ export const handler: PluginHandler = async (
parameters: PluginParameters,
eventType: HookEventType
) => {
console.log('[Javelin] Handler called with eventType:', eventType);
console.log('[Highflame] Handler called with eventType:', eventType);
console.log(
'[Javelin] Full parameters object:',
'[Highflame] Full parameters object:',
JSON.stringify(parameters, null, 2)
);
console.log('[Javelin] Parameters keys:', Object.keys(parameters));
console.log('[Highflame] Parameters keys:', Object.keys(parameters));

let error = null;
let verdict = true;
let data = null;

// Try multiple ways to get credentials
let credentials = parameters.credentials as unknown as JavelinCredentials;
let credentials = parameters.credentials as unknown as HighflameCredentials;

// If credentials not at root, check if they're nested or direct properties
if (!credentials || !credentials.apiKey) {
console.log('[Javelin] Credentials not found at parameters.credentials');
console.log('[Javelin] Trying direct properties...');
console.log('[Highflame] Credentials not found at parameters.credentials');
console.log('[Highflame] Trying direct properties...');

// Check if credentials are passed as direct properties
if (parameters.apiKey) {
console.log('[Javelin] Found credentials as direct properties');
console.log('[Highflame] Found credentials as direct properties');
credentials = {
apiKey: parameters.apiKey as string,
domain: parameters.domain as string | undefined,
Expand All @@ -120,7 +141,7 @@ export const handler: PluginHandler = async (
}
}

console.log('[Javelin] Final credentials check:', {
console.log('[Highflame] Final credentials check:', {
hasApiKey: !!credentials?.apiKey,
hasDomain: !!credentials?.domain,
hasApplication: !!credentials?.application,
Expand All @@ -130,7 +151,7 @@ export const handler: PluginHandler = async (
});

if (!credentials?.apiKey) {
console.error('[Javelin] Missing API key after all checks');
console.error('[Highflame] Missing API key after all checks');
return {
error: `'parameters.credentials.apiKey' must be set. Received parameters keys: ${Object.keys(parameters).join(', ')}`,
verdict: true,
Expand All @@ -139,7 +160,7 @@ export const handler: PluginHandler = async (
}

if (!credentials?.application) {
console.error('[Javelin] Missing application name');
console.error('[Highflame] Missing application name');
return {
error: `'parameters.credentials.application' must be set. Received: ${JSON.stringify(credentials)}`,
verdict: true,
Expand All @@ -149,7 +170,7 @@ export const handler: PluginHandler = async (

const { content, textArray } = getCurrentContentPart(context, eventType);
if (!content) {
console.error('[Javelin] No content to check');
console.error('[Highflame] No content to check');
return {
error: { message: 'request or response json is empty' },
verdict: true,
Expand All @@ -158,18 +179,27 @@ export const handler: PluginHandler = async (
}

const text = textArray.filter((text) => text).join('\n');
console.log('[Javelin] Text to check (length):', text.length);
console.log('[Highflame] Text to check (length):', text.length);

// Get optional guardrails config from parameters
const guardrails = parameters.guardrails as GuardrailConfig[] | undefined;
const threshold = parameters.threshold as number | undefined;

try {
const response = await callJavelinGuardrails(text, credentials);
const response = await callHighflameGuardrails(
text,
credentials,
guardrails,
threshold
);
const assessments = response.assessments || [];

console.log('[Javelin] Received', assessments.length, 'assessments');
console.log('[Highflame] Received', assessments.length, 'assessments');

if (assessments.length === 0) {
console.warn('[Javelin] No assessments in response');
console.warn('[Highflame] No assessments in response');
return {
error: { message: 'No assessments in Javelin response' },
error: { message: 'No assessments in Highflame response' },
verdict: true,
data: null,
};
Expand All @@ -191,7 +221,7 @@ export const handler: PluginHandler = async (
assessment
)) {
console.log(
'[Javelin] Assessment:',
'[Highflame] Assessment:',
assessmentType,
'request_reject:',
assessmentData.request_reject
Expand Down Expand Up @@ -223,10 +253,10 @@ export const handler: PluginHandler = async (
// Use a default message if no reject_prompt was found
if (!rejectPrompt) {
rejectPrompt =
'Request blocked by Javelin guardrails due to policy violation';
'Request blocked by Highflame guardrails due to policy violation';
}

console.log('[Javelin] Request REJECTED:', rejectPrompt);
console.log('[Highflame] Request REJECTED:', rejectPrompt);

// Return with verdict false and NO error field for policy violations
// Portkey will handle the deny logic based on guardrail actions
Expand All @@ -235,10 +265,10 @@ export const handler: PluginHandler = async (
data = {
flagged_assessments: flaggedAssessments,
reject_prompt: rejectPrompt,
javelin_response: response,
highflame_response: response,
};
} else {
console.log('[Javelin] Request PASSED all guardrails');
console.log('[Highflame] Request PASSED all guardrails');

// All guardrails passed
verdict = true;
Expand All @@ -250,12 +280,12 @@ export const handler: PluginHandler = async (
}
} catch (e: any) {
// Handle API errors - still return verdict true so Portkey doesn't block
console.error('[Javelin] Error calling API:', e.message);
console.error('[Javelin] Error details:', e);
console.error('[Highflame] Error calling API:', e.message);
console.error('[Highflame] Error details:', e);

// Create a serializable error object
error = {
message: e.message || 'Unknown error calling Javelin API',
message: e.message || 'Unknown error calling Highflame API',
name: e.name,
...(e.cause && { cause: e.cause }),
};
Expand All @@ -266,7 +296,7 @@ export const handler: PluginHandler = async (
};
}

console.log('[Javelin] Returning:', {
console.log('[Highflame] Returning:', {
verdict,
hasError: !!error,
hasData: !!data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
global.fetch = jest.fn();
import { handler as guardrailsHandler } from './guardrails';

describe('Javelin Guardrails Tests', () => {
describe('Highflame Guardrails Tests', () => {
beforeEach(() => {
jest.clearAllMocks();
});
Expand Down Expand Up @@ -159,7 +159,7 @@ describe('Javelin Guardrails Tests', () => {
expect(result.data.reject_prompt).toBe(
'Unable to complete request, trust & safety violation detected'
);
expect(result.data.javelin_response).toEqual(mockResponse);
expect(result.data.highflame_response).toEqual(mockResponse);
expect(result.data.flagged_assessments).toHaveLength(1);
expect(result.data.flagged_assessments[0].type).toBe('trustsafety');
expect(result.data.flagged_assessments[0].request_reject).toBe(true);
Expand Down Expand Up @@ -424,7 +424,7 @@ describe('Javelin Guardrails Tests', () => {

expect(result.verdict).toBe(false);
expect(result.error).toBe(
'Request blocked by Javelin guardrails due to policy violation'
'Request blocked by Highflame guardrails due to policy violation'
);
});

Expand Down
92 changes: 92 additions & 0 deletions plugins/highflame/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
{
"id": "highflame",
"description": "Highflame's AI security platform provides comprehensive guardrails for trust & safety, prompt injection detection, and language detection. Applies all enabled guardrails configured in your Highflame application policy. Learn more at https://docs.highflame.ai/",
"credentials": {
"type": "object",
"properties": {
"apiKey": {
"type": "string",
"label": "API Key",
"description": [
{
"type": "subHeading",
"text": "Your Highflame API key for authentication"
}
]
},
"domain": {
"type": "string",
"label": "Domain",
"description": [
{
"type": "subHeading",
"text": "Highflame API domain"
}
],
"default": "api-dev.highflame.dev",
"required": false
},
"application": {
"type": "string",
"label": "Application Name",
"description": [
{
"type": "subHeading",
"text": "Application name for policy-specific guardrails (required). See https://docs.highflame.ai/documentation/agent-control-fabric/guardrails-policies/guardrail-apis"
}
],
"required": true
}
},
"required": ["apiKey", "application"]
},
"functions": [
{
"name": "Highflame Guardrails",
"id": "guardrails",
"supportedHooks": ["beforeRequestHook", "afterRequestHook"],
"type": "guardrail",
"description": [
{
"type": "subHeading",
"text": "Auto-applies all enabled guardrails in your Highflame application policy including trust & safety, prompt injection detection, language detection, and more"
}
],
"parameters": {
"type": "object",
"properties": {
"guardrails": {
"type": "array",
"description": "List of guardrails to run. Defaults to trustsafety and promptinjectiondetection if not specified.",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"enum": [
"trustsafety",
"promptinjectiondetection",
"lang_detector",
"dlp_gcp",
"model_armor"
]
},
"config": {
"type": "object",
"properties": {
"threshold": { "type": "number" }
}
}
},
"required": ["name"]
}
},
"threshold": {
"type": "number",
"description": "Global threshold for all guardrails (0-1). Can be overridden per guardrail."
}
}
}
}
]
}
Loading