Skip to content

Commit b1cf872

Browse files
committed
feat: add optional service binding logic for portkey plugins
1 parent 0123c12 commit b1cf872

File tree

2 files changed

+99
-3
lines changed

2 files changed

+99
-3
lines changed

plugins/portkey/globals.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { post } from '../utils';
1+
import { getRuntimeKey } from 'hono/adapter';
2+
import { post, postWithCloudflareServiceBinding } from '../utils';
3+
4+
export const BASE_URL = 'https://api.portkey.ai/v1/execute-guardrails';
25

36
export const PORTKEY_ENDPOINTS = {
47
MODERATIONS: '/moderations',
@@ -8,15 +11,25 @@ export const PORTKEY_ENDPOINTS = {
811
};
912

1013
export const fetchPortkey = async (
14+
env: Record<string, any>,
1115
endpoint: string,
1216
credentials: any,
1317
data: any
1418
) => {
1519
const options = {
1620
headers: {
17-
Authorization: `Bearer ${credentials.apiKey}`,
21+
'x-portkey-api-key': credentials.apiKey,
1822
},
1923
};
2024

21-
return post(`${credentials.baseURL}${endpoint}`, data, options);
25+
if (getRuntimeKey() === 'workerd' && env.portkeyGuardrails) {
26+
return postWithCloudflareServiceBinding(
27+
`${BASE_URL}${endpoint}`,
28+
data,
29+
env.portkeyGuardrails,
30+
options
31+
);
32+
}
33+
34+
return post(`${BASE_URL}${endpoint}`, data, options);
2235
};

plugins/utils.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,86 @@ export async function post<T = any>(
129129
throw error;
130130
}
131131
}
132+
133+
/**
134+
* Sends a POST request to the specified URL with the given data and timeout.
135+
* @param url - The URL to send the POST request to.
136+
* @param data - The data to be sent in the request body.
137+
* @param options - Additional options for the fetch call.
138+
* @param timeout - Timeout in milliseconds (default: 5 seconds).
139+
* @returns A promise that resolves to the JSON response.
140+
* @throws {HttpError} Throws an HttpError with detailed information if the request fails.
141+
* @throws {Error} Throws a generic Error for network issues or timeouts.
142+
*/
143+
export async function postWithCloudflareServiceBinding<T = any>(
144+
url: string,
145+
data: any,
146+
serviceBinding: any,
147+
options: PostOptions = {},
148+
timeout: number = 5000
149+
): Promise<T> {
150+
const defaultOptions: PostOptions = {
151+
method: 'POST',
152+
headers: {
153+
'Content-Type': 'application/json',
154+
},
155+
body: JSON.stringify(data),
156+
};
157+
158+
const mergedOptions: PostOptions = { ...defaultOptions, ...options };
159+
160+
if (mergedOptions.headers) {
161+
mergedOptions.headers = {
162+
...defaultOptions.headers,
163+
...mergedOptions.headers,
164+
};
165+
}
166+
167+
try {
168+
const controller = new AbortController();
169+
const id = setTimeout(() => controller.abort(), timeout);
170+
171+
const response: Response = await serviceBinding.fetch(url, {
172+
...mergedOptions,
173+
signal: controller.signal,
174+
});
175+
176+
clearTimeout(id);
177+
178+
if (!response.ok) {
179+
let errorBody: string;
180+
try {
181+
errorBody = await response.text();
182+
} catch (e) {
183+
errorBody = 'Unable to retrieve response body';
184+
}
185+
186+
const errorResponse: ErrorResponse = {
187+
status: response.status,
188+
statusText: response.statusText,
189+
body: errorBody,
190+
};
191+
192+
throw new HttpError(
193+
`HTTP error! status: ${response.status}`,
194+
errorResponse
195+
);
196+
}
197+
198+
return (await response.json()) as T;
199+
} catch (error: any) {
200+
if (error instanceof HttpError) {
201+
throw error;
202+
}
203+
if (error.name === 'AbortError') {
204+
throw new TimeoutError(
205+
`Request timed out after ${timeout}ms`,
206+
url,
207+
timeout,
208+
mergedOptions.method || 'POST'
209+
);
210+
}
211+
// console.error('Error in post request:', error);
212+
throw error;
213+
}
214+
}

0 commit comments

Comments
 (0)