Files
wehub-resource-sync 3cd11ababe
Check Markdown links / linkChecker (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:38:56 +08:00

164 lines
3.7 KiB
TypeScript

import {
HookEventType,
PluginContext,
PluginHandler,
PluginParameters,
} from '../types';
import { post, HttpError } from '../utils';
import { VERSION } from './version';
export const handler: PluginHandler = async (
context: PluginContext,
parameters: PluginParameters,
eventType: HookEventType
) => {
let error = null;
let verdict = true;
let data = null;
if (!parameters.credentials?.baseUrl) {
return {
error: `'parameters.credentials.baseUrl' must be set`,
verdict: true,
data,
};
}
if (!parameters.credentials?.apiKey) {
return {
error: `'parameters.credentials.apiKey' must be set`,
verdict: true,
data,
};
}
const url = `${parameters.credentials.baseUrl}/v1/guard_chat_completions`;
const target = eventType === 'beforeRequestHook' ? 'request' : 'response';
const json = context[target].json;
const aidrEventType = target === 'request' ? 'input' : 'output';
const metadata = context.metadata ?? {};
const userId = metadata._user ?? metadata.user_id;
const userName = metadata._user_name ?? metadata.user_name;
const model = context.request?.json?.model;
const requestBody: Record<string, any> = {
guard_input: json,
event_type: aidrEventType,
app_id: 'Portkey AI Gateway',
extra_info: {},
};
if (userId) requestBody.user_id = userId;
if (context.provider) requestBody.llm_provider = context.provider;
if (model) requestBody.model = model;
if (userName) {
requestBody.extra_info = { ...requestBody.extra_info, user_name: userName };
}
const requestOptions: object = {
headers: {
'Content-Type': 'application/json',
'User-Agent': `portkey-ai-plugin/${VERSION}`,
Authorization: `Bearer ${parameters.credentials.apiKey}`,
},
};
let response;
try {
response = await post(url, requestBody, requestOptions, parameters.timeout);
} catch (e) {
if (e instanceof HttpError) {
error = `${e.message}. body: ${e.response.body}`;
} else {
error = e as Error;
}
}
if (!response) {
return {
error,
verdict,
data,
};
}
if (response.status != 'Success') {
error = errorToString(response);
return {
error,
verdict,
data,
};
}
const result = response.result;
if (!result) {
return {
error: `Missing result from response body: ${response}`,
verdict,
data,
};
}
if (result.blocked) {
data = {
explanation: `Blocked by AIDR Policy '${result.policy}'`,
};
return {
error,
verdict: false,
data,
};
}
if (!result.transformed) {
// Not blocked, not transformed, nothing else to do
data = {
explanation: `Allowed by AIDR Policy '${result.policy}'`,
};
return {
error,
verdict,
data,
};
}
data = {
explanation: `Allowed by AIDR policy '${result.policy}', but requires transformations`,
};
let transformedData: Record<string, any> = {
request: {
json: null,
},
response: {
json: null,
},
};
const redactedJson = result.guard_output;
transformedData[target].json = redactedJson;
// Apply transformations
return {
error,
verdict,
data,
transformedData,
transformed: true,
};
};
function errorToString(response: any): string {
let ret = `Summary: ${response.summary}\n`;
ret += `status: ${response.status}\n`;
ret += `request_id: ${response.request_id}\n`;
ret += `request_time: ${response.request_time}\n`;
ret += `response_time: ${response.response_time}\n`;
(response.result?.errors || []).forEach((ef: any) => {
ret += `\t${ef.source} ${ef.code}: ${ef.detail}\n`;
});
return ret;
}