This commit is contained in:
Alex Sadleir 2025-12-23 20:15:15 +11:00 committed by GitHub
commit 79ef8b3b49
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 182 additions and 1 deletions

View file

@ -617,6 +617,25 @@ export namespace Config {
.describe(
"Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
),
tls: z
.object({
rejectUnauthorized: z
.boolean()
.optional()
.describe("Set to false to accept self-signed certificates. Default is true."),
cert: z.string().optional().describe("Path to client certificate file (PEM format) for mTLS."),
key: z.string().optional().describe("Path to client private key file (PEM format) for mTLS."),
ca: z
.union([z.string(), z.array(z.string())])
.optional()
.describe("Path(s) to CA certificate file(s) (PEM format) to trust."),
})
.optional()
.describe("TLS configuration for secure connections, including mTLS and custom CA certificates."),
proxy: z
.string()
.optional()
.describe("Proxy URL for this provider (e.g., http://proxy.example.com:8080). Overrides environment variables."),
})
.catchall(z.any())
.optional(),

View file

@ -1,5 +1,7 @@
import z from "zod"
import fuzzysort from "fuzzysort"
import os from "os"
import path from "path"
import { Config } from "../config/config"
import { mapValues, mergeDeep, sortBy } from "remeda"
import { NoSuchModelError, type Provider as SDK } from "ai"
@ -799,9 +801,17 @@ export namespace Provider {
if (existing) return existing
const customFetch = options["fetch"]
const tlsConfig = options["tls"] as
| {
rejectUnauthorized?: boolean
cert?: string
key?: string
ca?: string | string[]
}
| undefined
const proxyUrl = options["proxy"] as string | undefined
options["fetch"] = async (input: any, init?: BunFetchRequestInit) => {
// Preserve custom fetch if it exists, wrap it with timeout logic
const fetchFn = customFetch ?? fetch
const opts = init ?? {}
@ -815,10 +825,39 @@ export namespace Provider {
opts.signal = combined
}
let tls: BunFetchRequestInit["tls"] | undefined
if (tlsConfig) {
tls = {}
if (tlsConfig.rejectUnauthorized !== undefined) {
tls.rejectUnauthorized = tlsConfig.rejectUnauthorized
}
if (tlsConfig.cert) {
const certPath = tlsConfig.cert.startsWith("~/")
? path.join(os.homedir(), tlsConfig.cert.slice(2))
: tlsConfig.cert
tls.cert = Bun.file(certPath)
}
if (tlsConfig.key) {
const keyPath = tlsConfig.key.startsWith("~/")
? path.join(os.homedir(), tlsConfig.key.slice(2))
: tlsConfig.key
tls.key = Bun.file(keyPath)
}
if (tlsConfig.ca) {
const caFiles = Array.isArray(tlsConfig.ca) ? tlsConfig.ca : [tlsConfig.ca]
tls.ca = caFiles.map((p) => {
const caPath = p.startsWith("~/") ? path.join(os.homedir(), p.slice(2)) : p
return Bun.file(caPath)
})
}
}
return fetchFn(input, {
...opts,
// @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682
timeout: false,
...(tls && { tls }),
...(proxyUrl && { proxy: proxyUrl }),
})
}

View file

@ -50,8 +50,131 @@ For proxies requiring advanced authentication like NTLM or Kerberos, consider us
If your enterprise uses custom CAs for HTTPS connections, configure OpenCode to trust them.
### System certificate store
Use certificates from your operating system's trusted certificate store (macOS Keychain, Windows Certificate Store, or Linux system certificates):
```bash
# Option 1: Set for all Bun executions
export BUN_OPTIONS="--use-system-ca"
# Option 2: Set via NODE_OPTIONS
export NODE_OPTIONS="--use-system-ca"
```
This is ideal for enterprise environments where IT has pre-installed certificates in the system store.
### Extra CA certificates
To add additional CA certificates without replacing the default bundle:
```bash
export NODE_EXTRA_CA_CERTS=/path/to/ca-cert.pem
```
This works for both proxy connections and direct API access.
---
## Per-provider proxy
Override the global proxy settings for a specific provider:
```json
{
"provider": {
"anthropic": {
"options": {
"proxy": "http://proxy.example.com:8080"
}
}
}
}
```
This takes precedence over the `HTTPS_PROXY` and `HTTP_PROXY` environment variables for that provider.
---
## Per-provider TLS configuration
For more granular control over TLS settings, you can configure TLS options per provider in your `opencode.json`:
### Self-signed certificates
Accept self-signed certificates for a specific provider:
```json
{
"provider": {
"my-proxy": {
"api": "@ai-sdk/openai-compatible",
"options": {
"baseURL": "https://my-internal-proxy.example.com",
"tls": {
"rejectUnauthorized": false
}
}
}
}
}
```
:::caution
Disabling certificate validation (`rejectUnauthorized: false`) bypasses TLS security checks. Only use this for trusted internal services.
:::
### Custom CA certificates
Trust a custom CA certificate for a provider:
```json
{
"provider": {
"my-proxy": {
"options": {
"baseURL": "https://my-internal-proxy.example.com",
"tls": {
"ca": "/path/to/ca-cert.pem"
}
}
}
}
}
```
You can also specify multiple CA certificates:
```json
{
"tls": {
"ca": ["/path/to/ca1.pem", "/path/to/ca2.pem"]
}
}
```
---
## mTLS (Mutual TLS)
For services requiring client certificate authentication, configure both the certificate and private key:
```json
{
"provider": {
"mtls-proxy": {
"api": "@ai-sdk/anthropic",
"options": {
"baseURL": "https://llm-proxy-mtls.internal.example.com",
"tls": {
"cert": "/path/to/client-cert.pem",
"key": "/path/to/client-key.pem",
"ca": "/path/to/ca-cert.pem"
}
}
}
}
}
```
This is useful for enterprise environments with mTLS proxies that provide access to LLM APIs.