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
7 changes: 7 additions & 0 deletions src/data/providers.json
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,13 @@
"description": "OpenRouter is an innovative platform that provides unified access to multiple large language models through a single interface. It enables businesses and developers to integrate diverse AI capabilities efficiently while focusing on scalability and cost-effectiveness in their applications.",
"base_url": "https://openrouter.ai/api"
},
{
"id": "requesty",
"name": "Requesty",
"object": "provider",
"description": "Requesty is an OpenAI-compatible LLM router that provides unified access to models from many providers through a single endpoint, using provider/model naming. It supports chat completions with streaming, tool calling, and reasoning outputs.",
"base_url": "https://router.requesty.ai/v1"
},
{
"id": "ovhcloud",
"name": "OVHcloud AI Endpoints",
Expand Down
2 changes: 2 additions & 0 deletions src/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export const FIREWORKS_AI: string = 'fireworks-ai';
export const WORKERS_AI: string = 'workers-ai';
export const MOONSHOT: string = 'moonshot';
export const OPENROUTER: string = 'openrouter';
export const REQUESTY: string = 'requesty';
export const LINGYI: string = 'lingyi';
export const ZHIPU: string = 'zhipu';
export const NOVITA_AI: string = 'novita-ai';
Expand Down Expand Up @@ -141,6 +142,7 @@ export const VALID_PROVIDERS = [
WORKERS_AI,
MOONSHOT,
OPENROUTER,
REQUESTY,
LINGYI,
ZHIPU,
NOVITA_AI,
Expand Down
2 changes: 2 additions & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import WorkersAiConfig from './workers-ai';
import RekaAIConfig from './reka-ai';
import MoonshotConfig from './moonshot';
import OpenrouterConfig from './openrouter';
import RequestyConfig from './requesty';
import LingYiConfig from './lingyi';
import ZhipuConfig from './zhipu';
import NovitaAIConfig from './novita-ai';
Expand Down Expand Up @@ -103,6 +104,7 @@ const Providers: { [key: string]: ProviderConfigs } = {
'reka-ai': RekaAIConfig,
moonshot: MoonshotConfig,
openrouter: OpenrouterConfig,
requesty: RequestyConfig,
lingyi: LingYiConfig,
zhipu: ZhipuConfig,
'novita-ai': NovitaAIConfig,
Expand Down
23 changes: 23 additions & 0 deletions src/providers/requesty/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { POWERED_BY } from '../../globals';
import { ProviderAPIConfig } from '../types';

const RequestyAPIConfig: ProviderAPIConfig = {
getBaseURL: () => 'https://router.requesty.ai',
headers: ({ providerOptions }) => {
return {
Authorization: `Bearer ${providerOptions.apiKey}`, // https://app.requesty.ai/api-keys
'HTTP-Referer': 'https://portkey.ai/',
'X-Title': POWERED_BY,
};
},
getEndpoint: ({ fn }) => {
switch (fn) {
case 'chatComplete':
return '/v1/chat/completions';
default:
return '';
}
},
};

export default RequestyAPIConfig;
289 changes: 289 additions & 0 deletions src/providers/requesty/chatComplete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
import { REQUESTY } from '../../globals';
import { Message, Params } from '../../types/requestBody';
import {
ChatChoice,
ChatCompletionResponse,
ErrorResponse,
ProviderConfig,
} from '../types';
import {
generateErrorResponse,
generateInvalidProviderResponseError,
} from '../utils';
import { transformReasoningParams, transformUsageOptions } from './utils';

export const RequestyChatCompleteConfig: ProviderConfig = {
model: {
param: 'model',
required: true,
default: 'openai/gpt-4o-mini',
},
messages: {
param: 'messages',
default: '',
transform: (params: Params) => {
return params.messages?.map((message) => {
if (message.role === 'developer') return { ...message, role: 'system' };
return message;
});
},
},
max_tokens: {
param: 'max_tokens',
default: 100,
min: 0,
},
max_completion_tokens: {
param: 'max_tokens',
default: 100,
min: 0,
},
temperature: {
param: 'temperature',
default: 1,
min: 0,
max: 2,
},
modalities: {
param: 'modalities',
},
reasoning: {
param: 'reasoning',
transform: (params: Params) => {
return transformReasoningParams(params);
},
},
reasoning_effort: {
param: 'reasoning',
transform: (params: Params) => {
return transformReasoningParams(params);
},
},
top_p: {
param: 'top_p',
default: 1,
min: 0,
max: 1,
},
tools: {
param: 'tools',
},
tool_choice: {
param: 'tool_choice',
},
usage: {
param: 'usage',
transform: (params: Params) => {
return transformUsageOptions(params);
},
},
stream: {
param: 'stream',
default: false,
},
stream_options: {
param: 'usage',
transform: (params: Params) => {
return transformUsageOptions(params);
},
},
response_format: {
param: 'response_format',
},
};

interface RequestyUsageDetails {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
prompt_tokens_details?: {
cached_tokens: number;
audio_tokens: number;
};
completion_tokens_details?: {
reasoning_tokens: number;
audio_tokens: number;
accepted_prediction_tokens: number;
rejected_prediction_tokens: number;
};
cost?: number;
}

interface RequestyChatCompleteResponse extends ChatCompletionResponse {
id: string;
object: string;
created: number;
model: string;
choices: (ChatChoice & {
message: Message & {
reasoning: string;
reasoning_details?: any[];
};
})[];
usage: RequestyUsageDetails;
}

export interface RequestyErrorResponse {
object: string;
message: string;
type: string;
param: string | null;
code: string;
}

interface RequestyStreamChunk {
id: string;
object: string;
created: number;
model: string;
usage?: RequestyUsageDetails;
choices: {
delta: {
role?: string | null;
content?: string;
reasoning?: string;
};
index: number;
finish_reason: string | null;
}[];
}

export const RequestyChatCompleteResponseTransform: (
response: RequestyChatCompleteResponse | RequestyErrorResponse,
responseStatus: number,
_responseHeaders: Headers,
strictOpenAiCompliance: boolean,
_gatewayRequestUrl: string,
_gatewayRequest: Params
) => ChatCompletionResponse | ErrorResponse = (
response,
responseStatus,
_responseHeaders,
strictOpenAiCompliance,
_gatewayRequestUrl,
_gatewayRequest
) => {
if ('message' in response && responseStatus !== 200) {
return generateErrorResponse(
{
message: response.message,
type: response.type,
param: response.param,
code: response.code,
},
REQUESTY
);
}

if ('choices' in response) {
return {
id: response.id,
object: response.object,
created: response.created,
model: response.model,
provider: REQUESTY,
choices: response.choices.map((c) => {
const content_blocks = [];

if (!strictOpenAiCompliance) {
if (c.message.reasoning) {
content_blocks.push({
type: 'thinking',
thinking: c.message.reasoning,
...(c.message.reasoning_details && {
reasoning_details: c.message.reasoning_details,
}),
});
}

content_blocks.push({
type: 'text',
text: c.message.content,
});
}

return {
index: c.index,
message: {
role: c.message.role,
content: c.message.content,
...(content_blocks.length && { content_blocks }),
...(c.message.tool_calls && { tool_calls: c.message.tool_calls }),
...(c.message.reasoning_details && {
reasoning_details: c.message.reasoning_details,
}),
},
finish_reason: c.finish_reason,
};
}),
usage: response.usage,
};
}

return generateInvalidProviderResponseError(response, REQUESTY);
};

export const RequestyChatCompleteStreamChunkTransform: (
response: string,
fallbackId: string,
_streamState: Record<string, boolean>,
_strictOpenAiCompliance: boolean,
gatewayRequest: Params
) => string = (
responseChunk,
fallbackId,
_streamState,
strictOpenAiCompliance,
gatewayRequest
) => {
let chunk = responseChunk.trim();
chunk = chunk.replace(/^data: /, '');
chunk = chunk.trim();
if (chunk === '[DONE]') {
return `data: ${chunk}\n\n`;
}
const parsedChunk: RequestyStreamChunk = JSON.parse(chunk);

const content_blocks = [];
if (!strictOpenAiCompliance) {
// add the reasoning first
if (parsedChunk.choices?.[0]?.delta?.reasoning) {
content_blocks.push({
index: parsedChunk.choices?.[0]?.index,
delta: {
thinking: parsedChunk.choices?.[0]?.delta?.reasoning,
},
});
}
// then add the content
if (parsedChunk.choices?.[0]?.delta?.content) {
content_blocks.push({
index: parsedChunk.choices?.[0]?.index,
delta: {
text: parsedChunk.choices?.[0]?.delta?.content,
},
});
}
}

return (
`data: ${JSON.stringify({
id: parsedChunk.id,
object: parsedChunk.object,
created: parsedChunk.created,
model: parsedChunk.model,
provider: REQUESTY,
choices: [
{
index: parsedChunk.choices?.[0]?.index,
delta: {
...parsedChunk.choices?.[0]?.delta,
...(content_blocks.length && { content_blocks }),
},
finish_reason: parsedChunk.choices?.[0]?.finish_reason,
},
],
...(parsedChunk.usage && { usage: parsedChunk.usage }),
})}` + '\n\n'
);
};
18 changes: 18 additions & 0 deletions src/providers/requesty/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ProviderConfigs } from '../types';
import RequestyAPIConfig from './api';
import {
RequestyChatCompleteConfig,
RequestyChatCompleteResponseTransform,
RequestyChatCompleteStreamChunkTransform,
} from './chatComplete';

const RequestyConfig: ProviderConfigs = {
chatComplete: RequestyChatCompleteConfig,
api: RequestyAPIConfig,
responseTransforms: {
chatComplete: RequestyChatCompleteResponseTransform,
'stream-chatComplete': RequestyChatCompleteStreamChunkTransform,
},
};

export default RequestyConfig;
Loading