Skip to content

Commit 827cfa9

Browse files
use built-in vercel-ai-gateway provider instead of custom providers
Remove models.providers from OpenClaw config. Instead, set AI_GATEWAY_API_KEY and AI_GATEWAY_BASE_URL in ~/.openclaw/.env so OpenClaw uses its built-in vercel-ai-gateway provider with auto-discovered models. The proxy handles auth, credit metering, and forwarding to Vercel AI Gateway. Made-with: Cursor
1 parent 87cd356 commit 827cfa9

4 files changed

Lines changed: 35 additions & 90 deletions

File tree

src/lib/hetzner/cloud-init.test.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,18 @@ describe('generateCloudInit', () => {
1919

2020
expect(result).toContain('#cloud-config')
2121
expect(result).toContain('/home/openclaw/.env')
22+
expect(result).toContain('/home/openclaw/.openclaw/.env')
2223
expect(result).toContain('/home/openclaw/.openclaw/openclaw.json')
2324
expect(result).toContain('/etc/systemd/system/ttyd.service')
2425
expect(result).toContain('/etc/systemd/system/openclaw-gateway.service')
2526
expect(result).toContain('ssh-rsa AAAA')
26-
expect(result).toContain(
27-
Buffer.from(
28-
'AI_GATEWAY_URL=https://app.example/api/gateway/proxy\n' +
29-
'INSTANCE_ID=inst-1\n' +
30-
'CUSTOMER_ID=org-1\n'
31-
).toString('base64')
32-
)
27+
28+
const openclawEnvB64 = Buffer.from(
29+
'AI_GATEWAY_API_KEY=gateway-token\n' +
30+
'AI_GATEWAY_BASE_URL=https://app.example/api/gateway/proxy\n'
31+
).toString('base64')
32+
expect(result).toContain(openclawEnvB64)
33+
3334
expect(result).toContain(Buffer.from('{"hello":"world"}').toString('base64'))
3435
expect(result).toContain('npx playwright install --with-deps chromium')
3536
expect(result).toContain('base64 -d > /etc/caddy/Caddyfile')

src/lib/hetzner/cloud-init.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,14 @@ function b64(text: string): string {
1414
return Buffer.from(text).toString('base64')
1515
}
1616

17+
function buildOpenClawEnv(params: CloudInitParams): string {
18+
return `AI_GATEWAY_API_KEY=${params.gatewayToken}
19+
AI_GATEWAY_BASE_URL=${params.proxyBaseUrl}
20+
`
21+
}
22+
1723
function buildEnvFile(params: CloudInitParams): string {
18-
return `AI_GATEWAY_URL=${params.proxyBaseUrl}
19-
INSTANCE_ID=${params.instanceId}
24+
return `INSTANCE_ID=${params.instanceId}
2025
CUSTOMER_ID=${params.customerId}
2126
`
2227
}
@@ -92,6 +97,7 @@ export function generateCloudInit(params: CloudInitParams): string {
9297
const hostname = `${params.slug}.${params.domain}`
9398

9499
const envFile = b64(buildEnvFile(params))
100+
const openclawEnv = b64(buildOpenClawEnv(params))
95101
const configFile = b64(params.openclawConfig)
96102
const caddyFile = b64(buildCaddyfile(hostname))
97103
const ttydService = b64(buildTtydService())
@@ -116,6 +122,10 @@ write_files:
116122
permissions: '0600'
117123
encoding: b64
118124
content: ${envFile}
125+
- path: /home/openclaw/.openclaw/.env
126+
permissions: '0600'
127+
encoding: b64
128+
content: ${openclawEnv}
119129
- path: /home/openclaw/.openclaw/openclaw.json
120130
permissions: '0644'
121131
encoding: b64

src/lib/openclaw/config.test.ts

Lines changed: 9 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function makeInstance(overrides: Partial<Instance> = {}): Instance {
5454
const defaultParams = {
5555
gatewayToken: 'gateway-token-xyz',
5656
dashboardUrl: 'https://my-instance.agentcomputers.app',
57-
proxyBaseUrl: 'https://clawcloud.dev/api/gateway/proxy',
57+
proxyBaseUrl: 'https://agentcomputers.app/api/gateway/proxy',
5858
}
5959

6060
describe('generateOpenClawConfig', () => {
@@ -65,53 +65,26 @@ describe('generateOpenClawConfig', () => {
6565
expect(config.gateway.auth.token).toBe('gateway-token-xyz')
6666
expect(config.gateway.bind).toBe('lan')
6767
expect(config.gateway.controlUi.allowedOrigins).toEqual(['*'])
68+
expect(config.gateway.controlUi.dangerouslyDisableDeviceAuth).toBe(true)
6869
})
6970

70-
it('sets default model with primary and fallbacks', () => {
71+
it('sets default model to vercel-ai-gateway provider', () => {
7172
const config = generateOpenClawConfig(makeInstance(), makeOrg(), defaultParams)
72-
expect(config.agents.defaults.model.primary).toBe('anthropic/claude-sonnet-4-5')
73-
expect(config.agents.defaults.model.fallbacks.length).toBeGreaterThan(0)
74-
expect(config.agents.defaults.model.fallbacks).not.toContain(config.agents.defaults.model.primary)
73+
expect(config.agents.defaults.model.primary).toBe('vercel-ai-gateway/anthropic/claude-sonnet-4-5')
7574
})
7675

77-
it('registers per-prefix providers pointing to proxy URL', () => {
76+
it('does not include models.providers (uses built-in vercel-ai-gateway)', () => {
7877
const config = generateOpenClawConfig(makeInstance(), makeOrg(), defaultParams)
79-
80-
for (const prefix of ['anthropic', 'openai', 'google']) {
81-
const provider = config.models.providers[prefix]
82-
expect(provider).toBeDefined()
83-
expect(provider.baseUrl).toBe('https://clawcloud.dev/api/gateway/proxy')
84-
expect(provider.api).toBe('openai-responses')
85-
expect(provider.models.length).toBeGreaterThan(0)
86-
expect(provider.models.every((m: { id: string }) => m.id.startsWith(`${prefix}/`))).toBe(true)
87-
}
88-
})
89-
90-
it('uses gateway token as API key for all providers', () => {
91-
const config = generateOpenClawConfig(makeInstance(), makeOrg(), defaultParams)
92-
93-
for (const prefix of ['anthropic', 'openai', 'google']) {
94-
expect(config.models.providers[prefix].apiKey).toBe('gateway-token-xyz')
95-
}
78+
expect((config as Record<string, unknown>).models).toBeUndefined()
9679
})
9780

98-
it('does not include any billing headers or secrets', () => {
81+
it('does not include any secrets in the config', () => {
9982
const config = generateOpenClawConfig(makeInstance(), makeOrg(), defaultParams)
10083
const configStr = JSON.stringify(config)
10184

10285
expect(configStr).not.toContain('stripe')
10386
expect(configStr).not.toContain('rk_test')
104-
expect(configStr).not.toContain('restricted')
105-
106-
for (const prefix of ['anthropic', 'openai', 'google']) {
107-
expect((config.models.providers[prefix] as unknown as Record<string, unknown>).headers).toBeUndefined()
108-
}
109-
})
110-
111-
it('works without stripe_customer_id (no longer required)', () => {
112-
const org = makeOrg({ stripe_customer_id: null })
113-
expect(() =>
114-
generateOpenClawConfig(makeInstance(), org, defaultParams)
115-
).not.toThrow()
87+
expect(configStr).not.toContain('AI_GATEWAY_API_KEY')
88+
expect(configStr).not.toContain('proxyBaseUrl')
11689
})
11790
})

src/lib/openclaw/config.ts

Lines changed: 6 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,37 +13,12 @@ export interface OpenClawConfig {
1313
}
1414
agents: {
1515
defaults: {
16-
model: { primary: string; fallbacks: string[] }
16+
model: { primary: string }
1717
}
1818
}
19-
models: {
20-
mode: string
21-
providers: Record<string, {
22-
baseUrl: string
23-
apiKey: string
24-
api: string
25-
models: Array<{ id: string; name: string }>
26-
}>
27-
}
28-
}
29-
30-
const DEFAULT_MODEL = 'anthropic/claude-sonnet-4-5'
31-
32-
const AVAILABLE_MODELS: Record<string, Array<{ id: string; name: string }>> = {
33-
anthropic: [
34-
{ id: 'anthropic/claude-sonnet-4-5', name: 'Claude Sonnet 4.5' },
35-
{ id: 'anthropic/claude-opus-4-6', name: 'Claude Opus 4.6' },
36-
],
37-
openai: [
38-
{ id: 'openai/gpt-4o', name: 'GPT-4o' },
39-
{ id: 'openai/o3-mini', name: 'o3-mini' },
40-
],
41-
google: [
42-
{ id: 'google/gemini-2.5-pro', name: 'Gemini 2.5 Pro' },
43-
],
4419
}
4520

46-
const ALL_MODELS = Object.values(AVAILABLE_MODELS).flat()
21+
const DEFAULT_MODEL = 'vercel-ai-gateway/anthropic/claude-sonnet-4-5'
4722

4823
interface ConfigParams {
4924
gatewayToken: string
@@ -54,9 +29,10 @@ interface ConfigParams {
5429
/**
5530
* Generate the OpenClaw configuration for a VPS instance.
5631
*
57-
* No secrets (AI gateway key, Stripe keys) are included — LLM requests
58-
* go through the ClawCloud proxy which holds secrets server-side.
59-
* The VPS authenticates to the proxy using its gateway_token.
32+
* Uses the built-in vercel-ai-gateway provider. The API key and base URL
33+
* are set via environment variables (AI_GATEWAY_API_KEY, AI_GATEWAY_BASE_URL)
34+
* in ~/.openclaw/.env, not in this config file. OpenClaw auto-discovers
35+
* all available models from the gateway.
6036
*/
6137
export function generateOpenClawConfig(
6238
_instance: Instance,
@@ -77,23 +53,8 @@ export function generateOpenClawConfig(
7753
defaults: {
7854
model: {
7955
primary: DEFAULT_MODEL,
80-
fallbacks: ALL_MODELS.filter(m => m.id !== DEFAULT_MODEL).map(m => m.id),
8156
},
8257
},
8358
},
84-
models: {
85-
mode: 'merge',
86-
providers: Object.fromEntries(
87-
Object.entries(AVAILABLE_MODELS).map(([provider, models]) => [
88-
provider,
89-
{
90-
baseUrl: params.proxyBaseUrl,
91-
apiKey: params.gatewayToken,
92-
api: 'openai-responses',
93-
models,
94-
},
95-
])
96-
),
97-
},
9859
}
9960
}

0 commit comments

Comments
 (0)