diff --git a/.env.example b/.env.example index b44266f..d69966f 100644 --- a/.env.example +++ b/.env.example @@ -31,12 +31,12 @@ REFRESH_INTERVAL_MINUTES=15 # === LLM Layer (optional) === # Enables AI-enhanced trade ideas and breaking news Telegram alerts. -# Provider options: anthropic | openai | gemini | codex | openrouter | minimax | mistral +# Provider options: anthropic | openai | gemini | codex | openrouter | minimax | mistral | grok LLM_PROVIDER= # Not needed for codex (uses ~/.codex/auth.json) LLM_API_KEY= # Optional override. Each provider has a sensible default: -# anthropic: claude-sonnet-4-6 | openai: gpt-5.4 | gemini: gemini-3.1-pro | codex: gpt-5.3-codex | openrouter: openrouter/auto | minimax: MiniMax-M2.5 +# anthropic: claude-sonnet-4-6 | openai: gpt-5.4 | gemini: gemini-3.1-pro | codex: gpt-5.3-codex | openrouter: openrouter/auto | minimax: MiniMax-M2.5 | grok: grok-3 LLM_MODEL= # === Telegram Alerts (optional, requires LLM) === diff --git a/README.md b/README.md index 89b1477..dbc5716 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ These three unlock the most valuable economic and satellite data. Each takes abo ### LLM Provider (optional, for AI-enhanced ideas) -Set `LLM_PROVIDER` to one of: `anthropic`, `openai`, `gemini`, `codex`, `openrouter`, `minimax`, `mistral` +Set `LLM_PROVIDER` to one of: `anthropic`, `openai`, `gemini`, `codex`, `openrouter`, `minimax`, `mistral`, `grok` | Provider | Key Required | Default Model | |----------|-------------|---------------| @@ -210,6 +210,7 @@ Set `LLM_PROVIDER` to one of: `anthropic`, `openai`, `gemini`, `codex`, `openrou | `codex` | None (uses `~/.codex/auth.json`) | gpt-5.3-codex | | `minimax` | `LLM_API_KEY` | MiniMax-M2.5 | | `mistral` | `LLM_API_KEY` | mistral-large-latest | +| `grok` | `LLM_API_KEY` | grok-3 | For Codex, run `npx @openai/codex login` to authenticate via your ChatGPT subscription. diff --git a/crucix.config.mjs b/crucix.config.mjs index c9e7235..aa35312 100644 --- a/crucix.config.mjs +++ b/crucix.config.mjs @@ -7,7 +7,7 @@ export default { refreshIntervalMinutes: parseInt(process.env.REFRESH_INTERVAL_MINUTES) || 15, llm: { - provider: process.env.LLM_PROVIDER || null, // anthropic | openai | gemini | codex | openrouter | minimax | mistral + provider: process.env.LLM_PROVIDER || null, // anthropic | openai | gemini | codex | openrouter | minimax | mistral | grok apiKey: process.env.LLM_API_KEY || null, model: process.env.LLM_MODEL || null, }, diff --git a/lib/llm/grok.mjs b/lib/llm/grok.mjs new file mode 100644 index 0000000..8fc4267 --- /dev/null +++ b/lib/llm/grok.mjs @@ -0,0 +1,53 @@ +// Grok Provider - raw fetch, no SDK + +import { LLMProvider } from './provider.mjs'; + +export class GrokProvider extends LLMProvider { + constructor(config) { + super(config); + this.name = 'grok'; + this.apiKey = config.apiKey; + this.model = config.model || 'grok-3'; + } + + getConfigured() { + return !!this.apiKey; + } + + async complete(systemPrompt, userMessage, opts = {}) { + const res = await fetch('https://api.x.ai/v1/chat/completions', { + method: POST, + headers: { + 'Content-type': 'application/json', + Authorization: `Bearer ${this.apiKey}`, + }, + body: JSON.stringify({ + messages: [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: userMessage }, + ], + model: this.model, + stream: false, + temperature: 0, + }), + signal: AbortSignal.timeout(opts.timeout || 60000), + }); + + if (!res.ok) { + const err = await res.text().catch(() => ''); + throw new Error(`Grok API ${res.status}: ${err.substring(0, 200)}`); + } + + const data = await res.json(); + const text = data.choices?.[0].message?.content || ''; + + return { + text, + usage: { + inputTokens: data.usage?.prompt_tokens || 0, + outputTokens: data.usage?.completion_tokens || 0, + }, + model: data.model || this.model, + }; + } +} diff --git a/lib/llm/index.mjs b/lib/llm/index.mjs index b2d16ee..096279c 100644 --- a/lib/llm/index.mjs +++ b/lib/llm/index.mjs @@ -7,6 +7,7 @@ import { GeminiProvider } from './gemini.mjs'; import { CodexProvider } from './codex.mjs'; import { MiniMaxProvider } from './minimax.mjs'; import { MistralProvider } from './mistral.mjs'; +import { GrokProvider } from './grok.mjs'; export { LLMProvider } from './provider.mjs'; export { AnthropicProvider } from './anthropic.mjs'; @@ -16,6 +17,8 @@ export { GeminiProvider } from './gemini.mjs'; export { CodexProvider } from './codex.mjs'; export { MiniMaxProvider } from './minimax.mjs'; export { MistralProvider } from './mistral.mjs'; +export { GrokProvider } from './grok.mjs'; + /** * Create an LLM provider based on config. @@ -42,6 +45,8 @@ export function createLLMProvider(llmConfig) { return new MiniMaxProvider({ apiKey, model }); case 'mistral': return new MistralProvider({ apiKey, model }); + case 'grok': + return new GrokProvider({ apiKey, model }); default: console.warn(`[LLM] Unknown provider "${provider}". LLM features disabled.`); return null;