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
84 changes: 81 additions & 3 deletions apps/public/content/docs/self-hosting/environment-variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ Available models:
- **OpenAI** — `GPT-4.1`, `GPT-4.1 mini`, `GPT-5.4 mini`
- **Anthropic** — `Claude Haiku 4.5`, `Claude Sonnet 4.6`, `Claude Opus 4.6`

<Callout type="info">
The AI assistant is optional. Without `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` set, every other OpenPanel feature continues to work normally.
</Callout>

### OPENAI_API_KEY

**Type**: `string`
Expand All @@ -282,6 +286,45 @@ OpenAI API key. When set, the OpenAI models appear in the chat model picker.
OPENAI_API_KEY=sk-your-openai-api-key-here
```

### OPENAI_BASE_URL

**Type**: `string`
**Required**: No
**Default**: OpenAI default API base URL

Override the OpenAI API base URL. Useful for proxies, gateways, Azure-compatible endpoints, or self-hosted OpenAI-compatible providers.

**Example**:
```bash
OPENAI_BASE_URL=https://your-openai-compatible-endpoint.example.com/v1
```

### OPENAI_PROJECT

**Type**: `string`
**Required**: No
**Default**: None

Optional OpenAI project identifier to send with requests.

**Example**:
```bash
OPENAI_PROJECT=proj_1234567890
```

### OPENAI_ORGANIZATION

**Type**: `string`
**Required**: No
**Default**: None

Optional OpenAI organization identifier to send with requests.

**Example**:
```bash
OPENAI_ORGANIZATION=org_1234567890
```

### ANTHROPIC_API_KEY

**Type**: `string`
Expand All @@ -295,9 +338,44 @@ Anthropic API key. When set, the Claude models appear in the chat model picker.
ANTHROPIC_API_KEY=sk-ant-your-anthropic-api-key-here
```

<Callout type="info">
The AI assistant is optional. Without `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` set, every other OpenPanel feature continues to work normally.
</Callout>
### ANTHROPIC_BASE_URL

**Type**: `string`
**Required**: No
**Default**: Anthropic default API base URL

Override the Anthropic API base URL. Useful for proxies, gateways, or Anthropic-compatible endpoints.

**Example**:
```bash
ANTHROPIC_BASE_URL=https://your-anthropic-endpoint.example.com
```

### ANTHROPIC_TOKEN

**Type**: `string`
**Required**: No
**Default**: None

Optional auth token sent to the Anthropic provider in addition to the API key. Use this only if your Anthropic-compatible gateway requires it.

**Example**:
```bash
ANTHROPIC_TOKEN=your-auth-token
```

### ANTHROPIC_VERSION

**Type**: `string`
**Required**: No
**Default**: Provider default

Override the Anthropic API version header sent with requests.

**Example**:
```bash
ANTHROPIC_VERSION=2023-06-01
```

## Email

Expand Down
65 changes: 52 additions & 13 deletions packages/trpc/src/agents/models.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,72 @@
import { createAnthropic } from '@better-agent/providers/anthropic';
import { createOpenAI } from '@better-agent/providers/openai';
import { CHAT_MODELS, type ChatModelEntry } from '@openpanel/validation';
import type { ChatModelEntry } from '@openpanel/validation';
import { z } from 'zod';

export { CHAT_MODELS as ALLOWED_MODELS } from '@openpanel/validation';
export type { ChatModelEntry } from '@openpanel/validation';
export { CHAT_MODELS as ALLOWED_MODELS } from '@openpanel/validation';

function requireEnv(name: string, provider: string): string {
const value = process.env[name];
if (!value) {
console.warn(
`[chat] ${name} is not set. Models routed through ${provider} will fail with "x-api-key required" until you add it to the API's env.`,
);
}
return value ?? '';
}
const openAiEnvSchema = z.object({
OPENAI_API_KEY: z.string().optional(),
OPENAI_BASE_URL: z.string().optional(),
OPENAI_PROJECT: z.string().optional(),
OPENAI_ORGANIZATION: z.string().optional(),
});

const anthropicEnvSchema = z.object({
ANTHROPIC_API_KEY: z.string().optional(),
ANTHROPIC_BASE_URL: z.string().optional(),
ANTHROPIC_TOKEN: z.string().optional(),
ANTHROPIC_VERSION: z.string().optional(),
});

let _openai: ReturnType<typeof createOpenAI> | null = null;
function openai() {
if (!_openai) {
_openai = createOpenAI({ apiKey: requireEnv('OPENAI_API_KEY', 'OpenAI') });
const {
OPENAI_API_KEY,
OPENAI_BASE_URL,
OPENAI_PROJECT,
OPENAI_ORGANIZATION,
} = openAiEnvSchema.parse(process.env);

if (!OPENAI_API_KEY) {
console.warn(
`[chat] OPENAI_API_KEY is not set. Models routed through OpenAI will fail with "x-api-key required" until you add it to the API's env.`
);
}

_openai = createOpenAI({
apiKey: OPENAI_API_KEY,
baseURL: OPENAI_BASE_URL,
project: OPENAI_PROJECT,
organization: OPENAI_ORGANIZATION,
});
}
return _openai;
}

let _anthropic: ReturnType<typeof createAnthropic> | null = null;
function anthropic() {
if (!_anthropic) {
const {
ANTHROPIC_API_KEY,
ANTHROPIC_BASE_URL,
ANTHROPIC_TOKEN,
ANTHROPIC_VERSION,
} = anthropicEnvSchema.parse(process.env);

if (!ANTHROPIC_API_KEY) {
console.warn(
`[chat] ANTHROPIC_API_KEY is not set. Models routed through Anthropic will fail with "x-api-key required" until you add it to the API's env.`
);
}

_anthropic = createAnthropic({
apiKey: requireEnv('ANTHROPIC_API_KEY', 'Anthropic'),
apiKey: ANTHROPIC_API_KEY,
baseURL: ANTHROPIC_BASE_URL,
authToken: ANTHROPIC_TOKEN,
anthropicVersion: ANTHROPIC_VERSION,
});
}
return _anthropic;
Expand Down