-
Notifications
You must be signed in to change notification settings - Fork 18
Updates #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Updates #10
Changes from all commits
cc5944c
c40fe4c
a3277e0
8ddbc72
ba850d4
50fb72c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import type { ProviderConfig } from './types'; | ||
|
|
||
| export const openrouter: ProviderConfig = { | ||
| id: 'openrouter', | ||
| label: 'OpenRouter', | ||
| baseUrl: 'https://openrouter.ai/api/v1', | ||
| keyHint: 'sk-or-...', | ||
| defaultModel: 'anthropic/claude-sonnet-4-5', | ||
| models: [ | ||
| { id: 'openrouter/auto', label: 'Auto (best for task)' }, | ||
| { id: 'anthropic/claude-opus-4-5', label: 'Claude Opus 4.5' }, | ||
| { id: 'anthropic/claude-sonnet-4-5', label: 'Claude Sonnet 4.5' }, | ||
| { id: 'openai/gpt-5.2', label: 'GPT-5.2' }, | ||
| { id: 'google/gemini-3-pro-preview', label: 'Gemini 3 Pro Preview' }, | ||
| { id: 'deepseek/deepseek-v3.2', label: 'DeepSeek V3.2' }, | ||
| ], | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,14 +22,21 @@ export function registerProxyRoutes( | |
| } | ||
| const vendorConfig = VENDOR_CONFIGS[vendor]; | ||
|
|
||
| // Extract and validate bot token | ||
| // Extract bot token from either Authorization header or x-api-key | ||
| // This supports both OpenAI-style (Bearer token) and Anthropic-style (x-api-key) auth | ||
| let botToken: string | undefined; | ||
|
|
||
| const auth = req.headers.authorization; | ||
| if (!auth?.startsWith('Bearer ')) { | ||
| if (auth?.startsWith('Bearer ')) { | ||
| botToken = auth.slice(7); | ||
| } else if (req.headers['x-api-key'] && typeof req.headers['x-api-key'] === 'string') { | ||
| botToken = req.headers['x-api-key']; | ||
|
Comment on lines
+32
to
+33
|
||
| } | ||
|
Comment on lines
+32
to
+34
|
||
|
|
||
| if (!botToken) { | ||
| reply.status(401).send({ error: 'Missing authorization' }); | ||
| return; | ||
| } | ||
|
|
||
| const botToken = auth.slice(7); | ||
| const tokenHash = hashToken(botToken); | ||
|
|
||
| // Lookup bot | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,7 +40,7 @@ export const VENDOR_CONFIGS: Record<string, VendorConfig> = { | |
| }, | ||
| anthropic: { | ||
| host: 'api.anthropic.com', | ||
| basePath: '/v1', | ||
| basePath: '', // OpenClaw's anthropic-messages API includes /v1 in its path | ||
|
||
| authHeader: 'x-api-key', | ||
| authFormat: (key) => key, | ||
| }, | ||
|
|
@@ -56,4 +56,10 @@ export const VENDOR_CONFIGS: Record<string, VendorConfig> = { | |
| authHeader: 'x-goog-api-key', | ||
| authFormat: (key) => key, | ||
| }, | ||
| openrouter: { | ||
| host: 'openrouter.ai', | ||
| basePath: '/api/v1', | ||
| authHeader: 'Authorization', | ||
| authFormat: (key) => `Bearer ${key}`, | ||
| }, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,6 +51,25 @@ export interface BotWorkspaceConfig { | |
| proxy?: ProxyConfig; | ||
| } | ||
|
|
||
| /** | ||
| * Map AI provider to OpenClaw API type. | ||
| * Each provider uses a different API format that OpenClaw must know about. | ||
| */ | ||
| function getApiTypeForProvider(provider: string): string { | ||
| switch (provider) { | ||
| case 'anthropic': | ||
| return 'anthropic-messages'; | ||
| case 'google': | ||
| return 'google-gemini'; | ||
|
Comment on lines
+62
to
+63
|
||
| case 'venice': | ||
| case 'openrouter': | ||
|
||
| return 'openai-completions'; // OpenAI-compatible APIs | ||
| case 'openai': | ||
| default: | ||
| return 'openai-responses'; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Generate openclaw.json configuration. | ||
| * Follows OpenClaw's expected config structure for gateway mode. | ||
|
|
@@ -72,7 +91,7 @@ function generateOpenclawConfig(config: BotWorkspaceConfig): object { | |
| [`${config.aiProvider}-proxy`]: { | ||
| baseUrl: config.proxy.baseUrl, | ||
| apiKey: config.proxy.token, | ||
| api: 'openai-responses', // Required for custom providers | ||
| api: getApiTypeForProvider(config.aiProvider), | ||
| models: [{ id: config.model, name: config.model }], | ||
| }, | ||
| }, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The authorization header extraction on line 29 doesn't check if the header value is a string before calling startsWith. While HTTP headers can be string or string arrays in Node.js/Fastify, calling startsWith on an array would cause a runtime error. Consider adding a type check similar to the x-api-key handling on line 32, for example: 'if (typeof auth === 'string' && auth.startsWith('Bearer '))'.