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
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const providerIconMap: Record<ProviderType, IconComponent | null> = {
'openai-compatible': OpenAI,
google: Gemini,
openrouter: OpenRouter,
requesty: null,
azure: Azure,
ollama: Ollama,
lmstudio: LmStudio,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,16 @@ export const providerTemplates: ProviderTemplate[] = [
setupGuideUrl:
'https://docs.browseros.com/features/bring-your-own-llm#openrouter',
}),
{
id: 'requesty',
name: 'Requesty',
defaultBaseUrl: 'https://router.requesty.ai/v1',
defaultModelId: 'anthropic/claude-sonnet-4-5',
supportsImages: true,
contextWindow: 200000,
apiKeyUrl: 'https://app.requesty.ai/api-keys',
setupGuideUrl: 'https://docs.requesty.ai',
},
enrichTemplate('lmstudio', {
defaultModelId: 'openai/gpt-oss-20b',
defaultBaseUrl: 'http://localhost:1234/v1',
Expand Down Expand Up @@ -168,6 +178,7 @@ export const providerTypeOptions: { value: ProviderType; label: string }[] = [
{ value: 'openai-compatible', label: 'OpenAI Compatible' },
{ value: 'google', label: 'Gemini' },
{ value: 'openrouter', label: 'OpenRouter' },
{ value: 'requesty', label: 'Requesty' },
{ value: 'azure', label: 'Azure' },
{ value: 'ollama', label: 'Ollama' },
{ value: 'lmstudio', label: 'LM Studio' },
Expand Down Expand Up @@ -203,6 +214,7 @@ const DEFAULT_BASE_URLS: Record<ProviderType, string> = {
'openai-compatible': '',
google: 'https://generativelanguage.googleapis.com/v1beta',
openrouter: 'https://openrouter.ai/api/v1',
requesty: 'https://router.requesty.ai/v1',
azure: '',
ollama: 'http://localhost:11434/v1',
lmstudio: 'http://localhost:1234/v1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type ProviderType =
| 'openai-compatible'
| 'google'
| 'openrouter'
| 'requesty'
| 'azure'
| 'ollama'
| 'lmstudio'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const providerTypeEnum = z.enum([
'openai-compatible',
'google',
'openrouter',
'requesty',
'azure',
'ollama',
'lmstudio',
Expand Down
1 change: 1 addition & 0 deletions packages/browseros-agent/apps/eval/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ Set eval environment variables in the monorepo root `.env.development` file.
| Azure OpenAI | `azure` | Yes |
| AWS Bedrock | `bedrock` | No |
| OpenRouter | `openrouter` | No |
| Requesty | `requesty` | No |
| Fireworks, Together, etc. | `openai-compatible` | Yes |
| Ollama | `ollama` | No |
| Clado Action (executor only) | `clado-action` | Yes |
Expand Down
2 changes: 1 addition & 1 deletion packages/browseros-agent/apps/server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Tools organized by category:

The agent loop uses the [Vercel AI SDK](https://sdk.vercel.ai) to orchestrate multi-step browser automation:

- **Multi-provider support** — OpenAI, Anthropic, Google, Azure, Bedrock, OpenRouter, Ollama, LM Studio, and any OpenAI-compatible endpoint
- **Multi-provider support** — OpenAI, Anthropic, Google, Azure, Bedrock, OpenRouter, Requesty, Ollama, LM Studio, and any OpenAI-compatible endpoint
- **Session management** — conversations persist in a local SQLite database
- **Context overflow handling** — automatic message compaction when context windows fill up
- **MCP client** — connects to external MCP servers for additional tool access (40+ app integrations)
Expand Down
18 changes: 18 additions & 0 deletions packages/browseros-agent/apps/server/src/agent/provider-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { createCodexFetch } from '../lib/clients/oauth/codex-fetch'
import { createCopilotFetch } from '../lib/clients/oauth/copilot-fetch'
import { logger } from '../lib/logger'
import { createOpenRouterCompatibleFetch } from '../lib/openrouter-fetch'
import { createRequestyCompatibleFetch } from '../lib/requesty-fetch'
import { ensureWorkspaceInstructionFile } from './acp-instructions/ensureInstructionFile'
import { ACP_PROVIDER_TYPES, isAcpProvider } from './acp-providers'
import type { BuildSystemPromptOptions } from './prompt'
Expand Down Expand Up @@ -300,6 +301,22 @@ function createOpenRouterFactory(
})
}

function createRequestyFactory(
config: ResolvedAgentConfig,
): (modelId: string) => unknown {
if (!config.apiKey) throw new Error('Requesty provider requires apiKey')
return createOpenAICompatible({
name: 'requesty',
baseURL: EXTERNAL_URLS.REQUESTY_API,
apiKey: config.apiKey,
headers: {
'HTTP-Referer': 'https://browseros.com',
'X-Title': 'BrowserOS',
},
fetch: createRequestyCompatibleFetch(),
})
}

function createAzureFactory(
config: ResolvedAgentConfig,
): (modelId: string) => unknown {
Expand Down Expand Up @@ -452,6 +469,7 @@ const PROVIDER_FACTORIES: Record<string, ProviderFactory> = {
[LLM_PROVIDERS.OPENAI]: createOpenAIFactory,
[LLM_PROVIDERS.GOOGLE]: createGoogleFactory,
[LLM_PROVIDERS.OPENROUTER]: createOpenRouterFactory,
[LLM_PROVIDERS.REQUESTY]: createRequestyFactory,
[LLM_PROVIDERS.AZURE]: createAzureFactory,
[LLM_PROVIDERS.LMSTUDIO]: createLMStudioFactory,
[LLM_PROVIDERS.OLLAMA]: createOllamaFactory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type { LanguageModel } from 'ai'
import { createBrowserOSFetch } from '../../browseros-fetch'
import { logger } from '../../logger'
import { createOpenRouterCompatibleFetch } from '../../openrouter-fetch'
import { createRequestyCompatibleFetch } from '../../requesty-fetch'
import { createCodexFetch } from '../oauth/codex-fetch'
import { createCopilotFetch } from '../oauth/copilot-fetch'
import {
Expand Down Expand Up @@ -53,6 +54,20 @@ function createOpenRouterModel(config: ResolvedLLMConfig): LanguageModel {
})(config.model)
}

function createRequestyModel(config: ResolvedLLMConfig): LanguageModel {
if (!config.apiKey) throw new Error('Requesty provider requires apiKey')
return createOpenAICompatible({
name: 'requesty',
baseURL: EXTERNAL_URLS.REQUESTY_API,
apiKey: config.apiKey,
headers: {
'HTTP-Referer': 'https://browseros.com',
'X-Title': 'BrowserOS',
},
fetch: createRequestyCompatibleFetch(),
})(config.model)
}

function createAzureModel(config: ResolvedLLMConfig): LanguageModel {
if (!config.apiKey || !config.resourceName) {
throw new Error('Azure provider requires apiKey and resourceName')
Expand Down Expand Up @@ -185,6 +200,7 @@ const PROVIDER_FACTORIES: Record<string, ProviderFactory> = {
[LLM_PROVIDERS.OPENAI]: createOpenAIModel,
[LLM_PROVIDERS.GOOGLE]: createGoogleModel,
[LLM_PROVIDERS.OPENROUTER]: createOpenRouterModel,
[LLM_PROVIDERS.REQUESTY]: createRequestyModel,
[LLM_PROVIDERS.AZURE]: createAzureModel,
[LLM_PROVIDERS.OLLAMA]: createOllamaModel,
[LLM_PROVIDERS.LMSTUDIO]: createLMStudioModel,
Expand Down
53 changes: 53 additions & 0 deletions packages/browseros-agent/apps/server/src/lib/requesty-fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { APICallError } from '@ai-sdk/provider'

/**
* Creates a fetch function that extracts detailed error messages from Requesty.
*
* Requesty is an OpenAI-compatible router (like OpenRouter). It can wrap
* upstream provider errors in a generic message, with the actual details
* nested under metadata.raw. This fetch intercepts HTTP errors and extracts
* the real error message so callers see the upstream failure.
*
* IMPORTANT: Throws APICallError (not plain Error) so the Vercel AI SDK's retry
* mechanism works correctly. The SDK's APICallError automatically calculates
* `isRetryable` from the statusCode (408, 409, 429, 500+ are retryable) - we
* don't override this default. Requesty's router occasionally returns transient
* 502s, so retryable handling matters here.
*/
export function createRequestyCompatibleFetch(): typeof fetch {
return (async (url: RequestInfo | URL, options?: RequestInit) => {
const response = await globalThis.fetch(url, options)

if (!response.ok) {
const statusCode = response.status
let errorMessage = `HTTP ${statusCode}: ${response.statusText}`
let responseBody: string | undefined

try {
responseBody = await response.clone().text()
const parsed = JSON.parse(responseBody)
if (parsed.error?.message) {
errorMessage = parsed.error.message
if (parsed.error.code) {
errorMessage = `[${parsed.error.code}] ${errorMessage}`
}
if (parsed.error.metadata?.raw) {
errorMessage += ` (${JSON.stringify(parsed.error.metadata.raw)})`
}
}
} catch {
// Keep default error message if parsing fails
}

throw new APICallError({
message: errorMessage,
url: typeof url === 'string' ? url : url.toString(),
requestBodyValues: {},
statusCode,
responseBody,
})
}

return response
}) as typeof fetch
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ export const EXTERNAL_URLS = {
QWEN_DEVICE_CODE: 'https://chat.qwen.ai/api/v1/oauth2/device/code',
QWEN_OAUTH_TOKEN: 'https://chat.qwen.ai/api/v1/oauth2/token',
QWEN_CODE_API: 'https://portal.qwen.ai/v1',
REQUESTY_API: 'https://router.requesty.ai/v1',
AGENT_CONTROL_WORKER: 'https://agent-control-worker.eng-6b4.workers.dev',
} as const
3 changes: 3 additions & 0 deletions packages/browseros-agent/packages/shared/src/schemas/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const LLM_PROVIDERS = {
OPENAI: 'openai',
GOOGLE: 'google',
OPENROUTER: 'openrouter',
REQUESTY: 'requesty',
AZURE: 'azure',
OLLAMA: 'ollama',
LMSTUDIO: 'lmstudio',
Expand All @@ -42,6 +43,7 @@ export const LLMProviderSchema: z.ZodEnum<
'openai',
'google',
'openrouter',
'requesty',
'azure',
'ollama',
'lmstudio',
Expand All @@ -62,6 +64,7 @@ export const LLMProviderSchema: z.ZodEnum<
LLM_PROVIDERS.OPENAI,
LLM_PROVIDERS.GOOGLE,
LLM_PROVIDERS.OPENROUTER,
LLM_PROVIDERS.REQUESTY,
LLM_PROVIDERS.AZURE,
LLM_PROVIDERS.OLLAMA,
LLM_PROVIDERS.LMSTUDIO,
Expand Down
Loading