Skip to content
Closed
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ pnpm test # run tests

All config lives in `data/config/` as JSON files with Zod validation. Missing files fall back to sensible defaults. You can edit these files directly or use the Web UI.

**AI Provider** — The default provider is Claude Code (`claude -p` subprocess). To use the [Vercel AI SDK](https://sdk.vercel.ai/docs) instead (Anthropic, OpenAI, Google, etc.), switch `ai-provider.json` to `vercel-ai-sdk` and add your API key to `api-keys.json`. A third option, Agent SDK (`@anthropic-ai/claude-agent-sdk`), is also available via `agent-sdk`.
**AI Provider** — The default provider is Claude Code (`claude -p` subprocess). To use the [Vercel AI SDK](https://sdk.vercel.ai/docs) instead (Anthropic, OpenAI, Google, [MiniMax](https://www.minimax.io), etc.), switch `ai-provider.json` to `vercel-ai-sdk` and add your API key to `api-keys.json`. A third option, Agent SDK (`@anthropic-ai/claude-agent-sdk`), is also available via `agent-sdk`.

**Trading** — Multi-account architecture. Crypto via [CCXT](https://docs.ccxt.com/) (Bybit, OKX, Binance, etc.) configured in `crypto.json`. US equities via [Alpaca](https://alpaca.markets/) configured in `securities.json`. Both use the same git-like trading workflow.

Expand All @@ -163,7 +163,7 @@ All config lives in `data/config/` as JSON files with Zod validation. Missing fi
| `engine.json` | Trading pairs, tick interval, timeframe |
| `agent.json` | Max agent steps, evolution mode toggle, Claude Code tool permissions |
| `ai-provider.json` | Active AI provider (`claude-code`, `vercel-ai-sdk`, or `agent-sdk`), switchable at runtime |
| `api-keys.json` | AI provider API keys (Anthropic, OpenAI, Google) — only needed for Vercel AI SDK mode |
| `api-keys.json` | AI provider API keys (Anthropic, OpenAI, Google, MiniMax) — only needed for Vercel AI SDK mode |
| `platforms.json` | Trading platform definitions (CCXT exchanges, Alpaca) |
| `accounts.json` | Trading account credentials and guard config, references platforms |
| `crypto.json` | CCXT exchange config + API keys, allowed symbols, guards |
Expand Down
11 changes: 10 additions & 1 deletion src/ai-providers/vercel-ai-sdk/model-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,16 @@ export async function createModelFromConfig(override?: ModelOverride): Promise<M
const client = createGoogleGenerativeAI({ apiKey: resolveApiKey('google'), baseURL: url || undefined })
return { model: client(m), key }
}
case 'minimax': {
const { createOpenAI } = await import('@ai-sdk/openai')
const client = createOpenAI({
apiKey: resolveApiKey('minimax') ?? process.env.MINIMAX_API_KEY,
baseURL: url || 'https://api.minimax.io/v1',
compatibility: 'compatible',
})
return { model: client.chat(m), key }
}
default:
throw new Error(`Unsupported model provider: "${p}". Supported: anthropic, openai, google`)
throw new Error(`Unsupported model provider: "${p}". Supported: anthropic, openai, google, minimax`)
}
}
1 change: 1 addition & 0 deletions src/connectors/web/routes/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function createConfigRoutes(opts?: ConfigRouteOpts) {
anthropic: !!config.apiKeys.anthropic,
openai: !!config.apiKeys.openai,
google: !!config.apiKeys.google,
minimax: !!config.apiKeys.minimax,
})
} catch (err) {
return c.json({ error: String(err) }, 500)
Expand Down
1 change: 1 addition & 0 deletions src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const aiProviderSchema = z.object({
anthropic: z.string().optional(),
openai: z.string().optional(),
google: z.string().optional(),
minimax: z.string().optional(),
}).default({}),
})

Expand Down
2 changes: 1 addition & 1 deletion ui/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export interface AIProviderConfig {
provider: string
model: string
baseUrl?: string
apiKeys: { anthropic?: string; openai?: string; google?: string }
apiKeys: { anthropic?: string; openai?: string; google?: string; minimax?: string }
}

export interface AppConfig {
Expand Down
12 changes: 10 additions & 2 deletions ui/src/pages/AIProviderPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@ const PROVIDER_MODELS: Record<string, { label: string; value: string }[]> = {
{ label: 'Gemini 3 Flash', value: 'gemini-3-flash-preview' },
{ label: 'Gemini 2.5 Pro', value: 'gemini-2.5-pro' },
],
minimax: [
{ label: 'MiniMax-M2.5', value: 'MiniMax-M2.5' },
{ label: 'MiniMax-M2.5-highspeed', value: 'MiniMax-M2.5-highspeed' },
],
}

const PROVIDERS = [
{ value: 'anthropic', label: 'Anthropic' },
{ value: 'openai', label: 'OpenAI' },
{ value: 'google', label: 'Google' },
{ value: 'minimax', label: 'MiniMax' },
{ value: 'custom', label: 'Custom' },
]

Expand Down Expand Up @@ -115,7 +120,7 @@ function ModelForm({ aiProvider }: { aiProvider: AIProviderConfig }) {
const [customModel, setCustomModel] = useState(initCustom ? (aiProvider.model || '') : '')
const [baseUrl, setBaseUrl] = useState(aiProvider.baseUrl || '')
const [showKeys, setShowKeys] = useState(false)
const [keys, setKeys] = useState({ anthropic: '', openai: '', google: '' })
const [keys, setKeys] = useState({ anthropic: '', openai: '', google: '', minimax: '' })
const [keySaveStatus, setKeySaveStatus] = useState<SaveStatus>('idle')
const keySavedTimer = useRef<ReturnType<typeof setTimeout> | null>(null)

Expand Down Expand Up @@ -152,6 +157,7 @@ function ModelForm({ aiProvider }: { aiProvider: AIProviderConfig }) {
anthropic: !!aiProvider.apiKeys?.anthropic,
openai: !!aiProvider.apiKeys?.openai,
google: !!aiProvider.apiKeys?.google,
minimax: !!aiProvider.apiKeys?.minimax,
}), [aiProvider.apiKeys])

const [liveKeyStatus, setLiveKeyStatus] = useState(keyStatus)
Expand Down Expand Up @@ -199,13 +205,15 @@ function ModelForm({ aiProvider }: { aiProvider: AIProviderConfig }) {
if (keys.anthropic) updatedKeys.anthropic = keys.anthropic
if (keys.openai) updatedKeys.openai = keys.openai
if (keys.google) updatedKeys.google = keys.google
if (keys.minimax) updatedKeys.minimax = keys.minimax
await api.config.updateSection('aiProvider', { ...aiProvider, apiKeys: updatedKeys })
setLiveKeyStatus({
anthropic: !!updatedKeys.anthropic,
openai: !!updatedKeys.openai,
google: !!updatedKeys.google,
minimax: !!updatedKeys.minimax,
})
setKeys({ anthropic: '', openai: '', google: '' })
setKeys({ anthropic: '', openai: '', google: '', minimax: '' })
setKeySaveStatus('saved')
if (keySavedTimer.current) clearTimeout(keySavedTimer.current)
keySavedTimer.current = setTimeout(() => setKeySaveStatus('idle'), 2000)
Expand Down