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
2 changes: 2 additions & 0 deletions src/api/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const ENV_KEY_MAP: Record<string, string> = {
openai: 'OPENAI_API_KEY',
anthropic: 'ANTHROPIC_API_KEY',
openrouter: 'OPENROUTER_API_KEY',
requesty: 'REQUESTY_API_KEY',
gemini: 'GEMINI_API_KEY',
groq: 'GROQ_API_KEY',
together: 'TOGETHER_API_KEY',
Expand All @@ -53,6 +54,7 @@ const ENV_KEY_MAP: Record<string, string> = {
const ENV_URL_MAP: Record<string, string> = {
openai: 'OPENAI_BASE_URL',
openrouter: 'OPENROUTER_BASE_URL',
requesty: 'REQUESTY_BASE_URL',
stability: 'STABILITY_BASE_URL',
replicate: 'REPLICATE_BASE_URL',
ollama: 'OLLAMA_BASE_URL',
Expand Down
16 changes: 15 additions & 1 deletion src/api/runtime/__tests__/provider-defaults.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { resolveModelOption, resolveProvider } from '../model.js';

describe('PROVIDER_DEFAULTS', () => {
it('has text model for all major providers', () => {
for (const id of ['openai', 'anthropic', 'ollama', 'openrouter', 'gemini']) {
for (const id of ['openai', 'anthropic', 'ollama', 'openrouter', 'gemini', 'requesty']) {
expect(PROVIDER_DEFAULTS[id]?.text).toBeDefined();
}
});
Expand Down Expand Up @@ -47,6 +47,7 @@ describe('autoDetectProvider', () => {
'TOGETHER_API_KEY',
'MISTRAL_API_KEY',
'XAI_API_KEY',
'REQUESTY_API_KEY',
'OLLAMA_BASE_URL',
'STABILITY_API_KEY',
'REPLICATE_API_TOKEN',
Expand All @@ -72,6 +73,19 @@ describe('autoDetectProvider', () => {
expect(autoDetectProvider()).toBe('openrouter');
});

it('detects requesty from REQUESTY_API_KEY', () => {
delete process.env.OPENAI_API_KEY;
delete process.env.ANTHROPIC_API_KEY;
delete process.env.OPENROUTER_API_KEY;
delete process.env.GEMINI_API_KEY;
delete process.env.GROQ_API_KEY;
delete process.env.TOGETHER_API_KEY;
delete process.env.MISTRAL_API_KEY;
delete process.env.XAI_API_KEY;
process.env.REQUESTY_API_KEY = 'requesty-test';
expect(autoDetectProvider()).toBe('requesty');
});

it('detects anthropic from ANTHROPIC_API_KEY', () => {
delete process.env.OPENAI_API_KEY;
delete process.env.OPENROUTER_API_KEY;
Expand Down
5 changes: 5 additions & 0 deletions src/api/runtime/provider-defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export const PROVIDER_DEFAULTS: Record<string, ProviderDefaults> = {
text: 'openai/gpt-4o',
cheap: 'openai/gpt-4o-mini',
},
requesty: {
text: 'openai/gpt-4o',
cheap: 'openai/gpt-4o-mini',
},
gemini: {
text: 'gemini-2.5-flash',
cheap: 'gemini-2.0-flash',
Expand Down Expand Up @@ -131,6 +135,7 @@ const AUTO_DETECT_ORDER: AutoDetectProbe[] = [
{ envKey: 'TOGETHER_API_KEY', provider: 'together' },
{ envKey: 'MISTRAL_API_KEY', provider: 'mistral' },
{ envKey: 'XAI_API_KEY', provider: 'xai' },
{ envKey: 'REQUESTY_API_KEY', provider: 'requesty' },
{ binaryName: 'claude', provider: 'claude-code-cli' },
{ binaryName: 'gemini', provider: 'gemini-cli' },
{ envKey: 'OLLAMA_BASE_URL', provider: 'ollama' },
Expand Down
19 changes: 19 additions & 0 deletions src/core/config/AgentOSConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface EnvironmentConfig {
OPENAI_API_KEY?: string;
ANTHROPIC_API_KEY?: string;
OPENROUTER_API_KEY?: string;
REQUESTY_API_KEY?: string;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
SERPER_API_KEY?: string;
OLLAMA_BASE_URL?: string;

Expand Down Expand Up @@ -97,6 +98,7 @@ export function validateEnvironmentConfig(env: Partial<EnvironmentConfig>): Conf
!env.OPENAI_API_KEY &&
!env.ANTHROPIC_API_KEY &&
!env.OPENROUTER_API_KEY &&
!env.REQUESTY_API_KEY &&
!env.OLLAMA_BASE_URL
) {
warnings.push('No LLM provider API keys configured. AgentOS will have limited functionality.');
Expand Down Expand Up @@ -128,6 +130,7 @@ export function getEnvironmentConfig(): EnvironmentConfig {
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY,
REQUESTY_API_KEY: process.env.REQUESTY_API_KEY,
OLLAMA_BASE_URL: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
LEMONSQUEEZY_API_KEY: process.env.LEMONSQUEEZY_API_KEY,
LEMONSQUEEZY_WEBHOOK_SECRET: process.env.LEMONSQUEEZY_WEBHOOK_SECRET,
Expand Down Expand Up @@ -231,6 +234,22 @@ function createModelProviderManagerConfig(env: EnvironmentConfig): AIModelProvid
});
}

// Requesty Provider (OpenAI-compatible LLM gateway)
if (env.REQUESTY_API_KEY) {
providers.push({
providerId: 'requesty',
enabled: true,
isDefault: !env.OPENAI_API_KEY && !env.OPENROUTER_API_KEY, // Default to Requesty if OpenAI/OpenRouter not available
config: {
apiKey: env.REQUESTY_API_KEY,
baseURL: 'https://router.requesty.ai/v1',
defaultModelId: 'openai/gpt-4o',
requestTimeout: 60000,
streamRequestTimeout: 180000,
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
}

// Ollama Provider (local)
if (env.OLLAMA_BASE_URL) {
providers.push({
Expand Down
6 changes: 5 additions & 1 deletion src/core/llm/providers/AIModelProviderManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import { IProvider, ModelInfo } from './IProvider';
import { OpenAIProvider, OpenAIProviderConfig } from './implementations/OpenAIProvider';
import { OpenRouterProvider, OpenRouterProviderConfig } from './implementations/OpenRouterProvider';
import { RequestyProvider, RequestyProviderConfig } from './implementations/RequestyProvider';
import { OllamaProvider, OllamaProviderConfig } from './implementations/OllamaProvider';
import { AnthropicProvider, AnthropicProviderConfig } from './implementations/AnthropicProvider';
import { GroqProvider, GroqProviderConfig } from './implementations/GroqProvider';
Expand All @@ -39,7 +40,7 @@ import { GMIError, GMIErrorCode, createGMIErrorFromError } from '../../utils/err
export interface ProviderConfigEntry {
providerId: string;
enabled: boolean;
config: Partial<OpenAIProviderConfig | OpenRouterProviderConfig | OllamaProviderConfig | AnthropicProviderConfig | GroqProviderConfig | TogetherProviderConfig | MistralProviderConfig | XAIProviderConfig | GeminiProviderConfig | ClaudeCodeProviderConfig | GeminiCLIProviderConfig | Record<string, any>>;
config: Partial<OpenAIProviderConfig | OpenRouterProviderConfig | RequestyProviderConfig | OllamaProviderConfig | AnthropicProviderConfig | GroqProviderConfig | TogetherProviderConfig | MistralProviderConfig | XAIProviderConfig | GeminiProviderConfig | ClaudeCodeProviderConfig | GeminiCLIProviderConfig | Record<string, any>>;
isDefault?: boolean;
}

Expand Down Expand Up @@ -126,6 +127,9 @@ export class AIModelProviderManager {
case 'openrouter':
providerInstance = new OpenRouterProvider();
break;
case 'requesty':
providerInstance = new RequestyProvider();
break;
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
case 'ollama':
providerInstance = new OllamaProvider();
break;
Expand Down
56 changes: 56 additions & 0 deletions src/core/llm/providers/errors/RequestyProviderError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// File: backend/agentos/core/llm/providers/errors/RequestyProviderError.ts
/**
* @fileoverview Defines a custom error class for Requesty-specific provider errors.
* This extends the base {@link ProviderError} to include details specific to Requesty API interactions.
* @module backend/agentos/core/llm/providers/errors/RequestyProviderError
* @see {@link ProviderError}
*/

import { ProviderError } from './ProviderError';

/**
* Represents an error specific to the Requesty provider.
* It can include additional context like HTTP status codes or specific Requesty error messages.
*
* @example
* try {
* // Requesty API call
* } catch (error) {
* if (error instanceof RequestyProviderError) {
* console.error(`Requesty Error (Status: ${error.httpStatus || 'N/A'}, Type: ${error.requestyErrorType || 'N/A'}): ${error.message}`);
* // Handle Requesty-specific error properties
* } else {
* // Handle other errors
* }
* }
*/
export class RequestyProviderError extends ProviderError {
/** HTTP status code from the API response (e.g., 400, 401, 429, 500). */
public readonly httpStatus?: number;

/** Requesty specific error type, if available from the response. */
public readonly requestyErrorType?: string;

/**
* Creates an instance of RequestyProviderError.
* @param {string} message - A human-readable description of the error.
* @param {string} code - A unique AgentOS internal code identifying the type of error (e.g., 'API_REQUEST_FAILED', 'INVALID_ROUTE').
* @param {number} [httpStatus] - HTTP status code from the API response.
* @param {string} [requestyErrorType] - Requesty specific error type.
* @param {unknown} [details] - Optional underlying error object or additional context from Requesty.
*/
constructor(
message: string,
code: string,
httpStatus?: number,
requestyErrorType?: string,
details?: unknown
) {
super(message, code, 'requesty', details); // ProviderId is 'requesty'
this.name = 'RequestyProviderError';
this.httpStatus = httpStatus;
this.requestyErrorType = requestyErrorType;

Object.setPrototypeOf(this, RequestyProviderError.prototype);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ export class OpenRouterProvider implements IProvider {
};
}
const finalMessage: ChatMessage = {
role: choice.delta?.role || accumulatedToolCalls.size > 0 ? 'assistant' : (choice.message?.role || 'assistant'),
role: choice.delta?.role || (accumulatedToolCalls.size > 0 ? 'assistant' : (choice.message?.role || 'assistant')),
content: responseTextDelta || (choice.message?.content || null),
tool_calls: Array.from(accumulatedToolCalls.values())
.filter(tc => tc.id && tc.function?.name)
Expand Down
Loading