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
16 changes: 16 additions & 0 deletions common/src/constants/requesty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Constants for the optional direct Requesty provider route.
*
* Requesty (https://requesty.ai) exposes an OpenAI-compatible router at
* https://router.requesty.ai/v1. When `REQUESTY_API_KEY` is set, the SDK can
* route chat completions directly to Requesty using the same OpenAI-compatible
* language model shim used elsewhere, instead of going through the Codebuff
* backend. Model ids use the same `provider/model` form as OpenRouter
* (e.g. `openai/gpt-4o-mini`, `anthropic/claude-sonnet-4.5`).
*/

/** Base URL for the Requesty OpenAI-compatible router. */
export const REQUESTY_BASE_URL = 'https://router.requesty.ai/v1'

/** Environment variable holding the Requesty API key. */
export const REQUESTY_ENV_VAR = 'REQUESTY_API_KEY'
22 changes: 22 additions & 0 deletions docs/agents-and-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,25 @@ base-lite "fix this bug"

- Tool definitions live in `common/src/tools` and are executed via the SDK helpers + agent-runtime.

## Model providers

Model ids use the `provider/model` form (e.g. `openai/gpt-4o-mini`,
`anthropic/claude-sonnet-4.5`). By default, requests are sent through the
Codebuff backend, which routes to the upstream provider (OpenRouter).

### Requesty (direct route)

[Requesty](https://requesty.ai) is an OpenAI-compatible router. Setting the
`REQUESTY_API_KEY` environment variable routes chat completions directly to the
Requesty router (`https://router.requesty.ai/v1`) instead of the Codebuff
backend, using the same `provider/model` ids. This mirrors the existing direct
ChatGPT OAuth route in `sdk/src/impl/model-provider.ts`.

- Router base URL: `https://router.requesty.ai/v1`
- API keys: https://app.requesty.ai/api-keys
- Model list: https://app.requesty.ai/router/list
- Docs: https://docs.requesty.ai

When `REQUESTY_API_KEY` is unset, behavior is unchanged.


25 changes: 25 additions & 0 deletions sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,31 @@ The `RunState` object contains:
- `sessionState`: Internal state to be passed to the next run
- `output`: The agent's output (text, error, or other types)

## Model providers

By default, the SDK sends requests through the Codebuff backend, which routes to
the upstream model provider (OpenRouter). Model ids use the `provider/model`
form, e.g. `openai/gpt-4o-mini` or `anthropic/claude-sonnet-4.5`.

### Requesty (direct, OpenAI-compatible)

[Requesty](https://requesty.ai) exposes an OpenAI-compatible router. When the
`REQUESTY_API_KEY` environment variable is set, the SDK routes chat completions
directly to the Requesty router (`https://router.requesty.ai/v1`) instead of the
Codebuff backend, using the same `provider/model` ids:

```bash
export REQUESTY_API_KEY="sk-..." # from https://app.requesty.ai/api-keys
```

- Router base URL: `https://router.requesty.ai/v1`
- API keys: https://app.requesty.ai/api-keys
- Available models: https://app.requesty.ai/router/list
- Docs: https://docs.requesty.ai

When `REQUESTY_API_KEY` is unset, behavior is unchanged (requests go through the
Codebuff backend).

## License

MIT
11 changes: 11 additions & 0 deletions sdk/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { BYOK_OPENROUTER_ENV_VAR } from '@codebuff/common/constants/byok'
import { CHATGPT_OAUTH_TOKEN_ENV_VAR } from '@codebuff/common/constants/chatgpt-oauth'
import { REQUESTY_ENV_VAR } from '@codebuff/common/constants/requesty'
import { API_KEY_ENV_VAR } from '@codebuff/common/constants/paths'
import { getBaseEnv } from '@codebuff/common/env-process'

Expand Down Expand Up @@ -42,6 +43,16 @@ export const getByokOpenrouterApiKeyFromEnv = (): string | undefined => {
return process.env[BYOK_OPENROUTER_ENV_VAR]
}

/**
* Get the Requesty API key from the environment.
*
* When set, the SDK routes chat completions directly to the Requesty
* OpenAI-compatible router instead of going through the Codebuff backend.
*/
export const getRequestyApiKeyFromEnv = (): string | undefined => {
return process.env[REQUESTY_ENV_VAR]
}

/**
* Get ChatGPT OAuth token from environment variable.
*/
Expand Down
56 changes: 56 additions & 0 deletions sdk/src/impl/__tests__/model-provider-requesty.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { describe, expect, test, beforeEach, afterEach } from 'bun:test'

import { REQUESTY_ENV_VAR } from '@codebuff/common/constants/requesty'

describe('getModelForRequest Requesty direct route', () => {
const previousRequestyKey = process.env[REQUESTY_ENV_VAR]

beforeEach(() => {
delete process.env[REQUESTY_ENV_VAR]
})

afterEach(() => {
if (previousRequestyKey === undefined) {
delete process.env[REQUESTY_ENV_VAR]
} else {
process.env[REQUESTY_ENV_VAR] = previousRequestyKey
}
})

async function importFresh() {
const mod = await import('../model-provider')
mod.resetChatGptOAuthRateLimit()
return mod
}

test('routes to Requesty when REQUESTY_API_KEY is set', async () => {
process.env[REQUESTY_ENV_VAR] = 'test-requesty-key'

const { getModelForRequest } = await importFresh()

const result = await getModelForRequest({
apiKey: 'test-codebuff-key',
model: 'openai/gpt-4o-mini',
})

expect(result.isChatGptOAuth).toBe(false)
expect(typeof result.model).not.toBe('string')
if (typeof result.model !== 'string') {
expect(result.model.provider).toBe('requesty')
}
})

test('uses Codebuff backend when REQUESTY_API_KEY is not set', async () => {
const { getModelForRequest } = await importFresh()

const result = await getModelForRequest({
apiKey: 'test-codebuff-key',
model: 'openai/gpt-4o-mini',
})

expect(result.isChatGptOAuth).toBe(false)
if (typeof result.model !== 'string') {
expect(result.model.provider).toBe('codebuff')
}
})
})
42 changes: 41 additions & 1 deletion sdk/src/impl/model-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import path from 'path'

import { BYOK_OPENROUTER_HEADER } from '@codebuff/common/constants/byok'
import { isFreeMode } from '@codebuff/common/constants/free-agents'
import { REQUESTY_BASE_URL } from '@codebuff/common/constants/requesty'
import {
CHATGPT_BACKEND_BASE_URL,
CHATGPT_OAUTH_ENABLED,
Expand All @@ -24,7 +25,10 @@ import {

import { WEBSITE_URL } from '../constants'
import { getValidChatGptOAuthCredentials } from '../credentials'
import { getByokOpenrouterApiKeyFromEnv } from '../env'
import {
getByokOpenrouterApiKeyFromEnv,
getRequestyApiKeyFromEnv,
} from '../env'
import {
createChatGptBackendFetch,
extractChatGptAccountId,
Expand Down Expand Up @@ -117,6 +121,18 @@ export async function getModelForRequest(
): Promise<ModelResult> {
const { apiKey, model, skipChatGptOAuth, costMode } = params

// Direct Requesty route: when REQUESTY_API_KEY is set, send chat completions
// straight to the Requesty OpenAI-compatible router instead of the Codebuff
// backend. Model ids use the same `provider/model` form (e.g.
// "openai/gpt-4o-mini"), so no remapping is required.
const requestyApiKey = getRequestyApiKeyFromEnv()
if (requestyApiKey) {
return {
model: createRequestyModel(model, requestyApiKey),
isChatGptOAuth: false,
}
}

// Check if we should use ChatGPT OAuth direct
// Only attempt for allowlisted models; non-allowlisted models silently fall through to backend.
if (
Expand Down Expand Up @@ -191,6 +207,30 @@ function createOpenAIOAuthModel(
})
}

/**
* Create a model that routes directly to the Requesty OpenAI-compatible router
* (https://router.requesty.ai/v1). Used when REQUESTY_API_KEY is set.
*
* This mirrors createOpenAIOAuthModel: it builds an OpenAICompatibleChatLanguageModel
* pointed at a fixed upstream base URL with a Bearer API key, rather than going
* through the Codebuff backend. Model ids use the same `provider/model` form as
* OpenRouter (e.g. "openai/gpt-4o-mini").
*/
function createRequestyModel(model: string, apiKey: string): LanguageModel {
return new OpenAICompatibleChatLanguageModel(model, {
provider: 'requesty',
url: ({ path }) => `${REQUESTY_BASE_URL}${path}`,
headers: () => ({
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'user-agent': `ai-sdk/openai-compatible/${VERSION}/codebuff-requesty`,
}),
fetch: undefined,
includeUsage: undefined,
supportsStructuredOutputs: true,
})
}

/**
* Create a model that routes through the Codebuff backend.
* This is the existing behavior - requests go to Codebuff backend which forwards to OpenRouter.
Expand Down