-
Notifications
You must be signed in to change notification settings - Fork 98
feat: add MiniMax as first-class LLM provider #231
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
base: main
Are you sure you want to change the base?
Changes from all commits
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,90 @@ | ||
| import { describe, expect, test } from 'vitest'; | ||
| import { PROVIDER_PRESETS, clampTemperature } from '@/utils/ai'; | ||
|
|
||
| /** | ||
| * Integration tests for MiniMax provider support. | ||
| * | ||
| * These tests verify that the MiniMax provider preset integrates correctly | ||
| * with the provider factory and that environment variable resolution | ||
| * produces the expected configuration. | ||
| * | ||
| * NOTE: Tests that hit the live MiniMax API require MINIMAX_API_KEY to be | ||
| * set. They are skipped when the key is not available. | ||
| */ | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Integration tests — preset resolution logic | ||
| // --------------------------------------------------------------------------- | ||
| describe('Provider preset resolution', () => { | ||
| test('minimax preset resolves base URL without AI_BASE_URL override', () => { | ||
| // Simulates what happens in ai.ts when AI_PROVIDER=minimax and | ||
| // AI_BASE_URL is not set. | ||
| const provider = 'minimax'; | ||
| const preset = PROVIDER_PRESETS[provider]; | ||
| const baseURL = undefined || preset?.baseURL || 'https://openrouter.ai/api/v1'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify no constant-left `undefined ||` remains in this test file.
rg -nP 'undefined\s*\|\|' tests/provider.integration.test.ts
# Expected result after fix: no matchesRepository: plastic-labs/tutor-gpt Length of output: 389 🏁 Script executed: # Search for ESLint configuration files
fd -e '.eslintrc*' -e 'eslint.config.*' -type fRepository: plastic-labs/tutor-gpt Length of output: 236 🏁 Script executed: # Check package.json for lint scripts and dependencies
cat package.json | grep -A 20 '"scripts"' | head -30Repository: plastic-labs/tutor-gpt Length of output: 927 🏁 Script executed: # View the full test file to see context around the flagged lines
wc -l tests/provider.integration.test.tsRepository: plastic-labs/tutor-gpt Length of output: 103 🏁 Script executed: # Find ESLint configuration properly
find . -maxdepth 2 -name '.eslintrc*' -o -name 'eslint.config.*' 2>/dev/nullRepository: plastic-labs/tutor-gpt Length of output: 85 🏁 Script executed: # View the test file around the flagged lines
cat -n tests/provider.integration.test.tsRepository: plastic-labs/tutor-gpt Length of output: 4290 🏁 Script executed: # Check the ESLint configuration
cat eslint.config.mjsRepository: plastic-labs/tutor-gpt Length of output: 806 Remove unnecessary Lines 24, 31, 38, and 39 contain Proposed fix- const baseURL = undefined || preset?.baseURL || 'https://openrouter.ai/api/v1';
+ const baseURL = preset?.baseURL || 'https://openrouter.ai/api/v1';- const model = undefined || preset?.defaultModel || 'gpt-3.5-turbo';
+ const model = preset?.defaultModel || 'gpt-3.5-turbo';Also applies to: 31-31, 38-39 🧰 Tools🪛 ESLint[error] 24-24: Unexpected constant truthiness on the left-hand side of a (no-constant-binary-expression) 🤖 Prompt for AI Agents |
||
| expect(baseURL).toBe('https://api.minimax.io/v1'); | ||
| }); | ||
|
|
||
| test('minimax preset resolves default model without MODEL override', () => { | ||
| const provider = 'minimax'; | ||
| const preset = PROVIDER_PRESETS[provider]; | ||
| const model = undefined || preset?.defaultModel || 'gpt-3.5-turbo'; | ||
| expect(model).toBe('MiniMax-M2.7'); | ||
| }); | ||
|
|
||
| test('unknown provider falls back to openrouter defaults', () => { | ||
| const provider = 'custom-provider'; | ||
| const preset = PROVIDER_PRESETS[provider]; | ||
| const baseURL = undefined || preset?.baseURL || 'https://openrouter.ai/api/v1'; | ||
| const model = undefined || preset?.defaultModel || 'gpt-3.5-turbo'; | ||
| expect(baseURL).toBe('https://openrouter.ai/api/v1'); | ||
| expect(model).toBe('gpt-3.5-turbo'); | ||
| }); | ||
| }); | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Integration tests — temperature clamping with provider presets | ||
| // --------------------------------------------------------------------------- | ||
| describe('Temperature clamping integration', () => { | ||
| test('minimax provider clamps temperature across boundary values', () => { | ||
| const provider = 'minimax'; | ||
| const testCases = [ | ||
| { input: 0, expected: 0.01 }, | ||
| { input: 0.5, expected: 0.5 }, | ||
| { input: 1, expected: 1 }, | ||
| { input: 1.5, expected: 1 }, | ||
| { input: -0.5, expected: 0.01 }, | ||
| ]; | ||
|
|
||
| for (const { input, expected } of testCases) { | ||
| expect(clampTemperature(provider, input)).toBe(expected); | ||
| } | ||
| }); | ||
|
|
||
| test('openrouter provider does not clamp temperature', () => { | ||
| const provider = 'openrouter'; | ||
| expect(clampTemperature(provider, 0)).toBe(0); | ||
| expect(clampTemperature(provider, 2)).toBe(2); | ||
| }); | ||
| }); | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Integration tests — OpenAI-compatible endpoint construction | ||
| // --------------------------------------------------------------------------- | ||
| describe('MiniMax API endpoint compatibility', () => { | ||
| test('minimax base URL follows OpenAI-compatible /v1 convention', () => { | ||
| const preset = PROVIDER_PRESETS.minimax; | ||
| expect(preset.baseURL).toMatch(/\/v1$/); | ||
| }); | ||
|
|
||
| test('minimax base URL uses HTTPS', () => { | ||
| const preset = PROVIDER_PRESETS.minimax; | ||
| expect(preset.baseURL).toMatch(/^https:\/\//); | ||
| }); | ||
|
|
||
| test('minimax base URL points to api.minimax.io', () => { | ||
| const preset = PROVIDER_PRESETS.minimax; | ||
| const url = new URL(preset.baseURL); | ||
| expect(url.hostname).toBe('api.minimax.io'); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| import { describe, expect, test } from 'vitest'; | ||
| import { PROVIDER_PRESETS, clampTemperature } from '@/utils/ai'; | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Unit tests — PROVIDER_PRESETS | ||
| // --------------------------------------------------------------------------- | ||
| describe('PROVIDER_PRESETS', () => { | ||
| test('has openrouter preset with correct base URL', () => { | ||
| expect(PROVIDER_PRESETS.openrouter).toBeDefined(); | ||
| expect(PROVIDER_PRESETS.openrouter.baseURL).toBe( | ||
| 'https://openrouter.ai/api/v1' | ||
| ); | ||
| expect(PROVIDER_PRESETS.openrouter.defaultModel).toBe('gpt-3.5-turbo'); | ||
| }); | ||
|
|
||
| test('openrouter preset includes HTTP-Referer header', () => { | ||
| expect(PROVIDER_PRESETS.openrouter.headers).toBeDefined(); | ||
| expect(PROVIDER_PRESETS.openrouter.headers!['HTTP-Referer']).toBe( | ||
| 'https://chat.bloombot.ai' | ||
| ); | ||
| expect(PROVIDER_PRESETS.openrouter.headers!['X-Title']).toBe('Bloombot'); | ||
| }); | ||
|
|
||
| test('has minimax preset with correct base URL', () => { | ||
| expect(PROVIDER_PRESETS.minimax).toBeDefined(); | ||
| expect(PROVIDER_PRESETS.minimax.baseURL).toBe( | ||
| 'https://api.minimax.io/v1' | ||
| ); | ||
| expect(PROVIDER_PRESETS.minimax.defaultModel).toBe('MiniMax-M2.7'); | ||
| }); | ||
|
|
||
| test('minimax preset does not include extra headers', () => { | ||
| expect(PROVIDER_PRESETS.minimax.headers).toBeUndefined(); | ||
| }); | ||
|
|
||
| test('presets contain distinct base URLs', () => { | ||
| const urls = Object.values(PROVIDER_PRESETS).map((p) => p.baseURL); | ||
| expect(new Set(urls).size).toBe(urls.length); | ||
| }); | ||
|
|
||
| test('preset models are non-empty strings', () => { | ||
| for (const [name, preset] of Object.entries(PROVIDER_PRESETS)) { | ||
| expect(preset.defaultModel.length).toBeGreaterThan(0); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Unit tests — clampTemperature | ||
| // --------------------------------------------------------------------------- | ||
| describe('clampTemperature', () => { | ||
| test('returns undefined when temperature is undefined', () => { | ||
| expect(clampTemperature('minimax', undefined)).toBeUndefined(); | ||
| expect(clampTemperature('openrouter', undefined)).toBeUndefined(); | ||
| }); | ||
|
|
||
| test('passes through temperature for non-minimax providers', () => { | ||
| expect(clampTemperature('openrouter', 0)).toBe(0); | ||
| expect(clampTemperature('openrouter', 0.5)).toBe(0.5); | ||
| expect(clampTemperature('openrouter', 2)).toBe(2); | ||
| expect(clampTemperature('custom', 0)).toBe(0); | ||
| }); | ||
|
|
||
| test('clamps zero to 0.01 for minimax', () => { | ||
| expect(clampTemperature('minimax', 0)).toBe(0.01); | ||
| }); | ||
|
|
||
| test('clamps negative to 0.01 for minimax', () => { | ||
| expect(clampTemperature('minimax', -1)).toBe(0.01); | ||
| }); | ||
|
|
||
| test('clamps values above 1 to 1 for minimax', () => { | ||
| expect(clampTemperature('minimax', 1.5)).toBe(1); | ||
| expect(clampTemperature('minimax', 2)).toBe(1); | ||
| }); | ||
|
|
||
| test('keeps valid minimax temperatures unchanged', () => { | ||
| expect(clampTemperature('minimax', 0.5)).toBe(0.5); | ||
| expect(clampTemperature('minimax', 0.7)).toBeCloseTo(0.7); | ||
| expect(clampTemperature('minimax', 1)).toBe(1); | ||
| expect(clampTemperature('minimax', 0.01)).toBe(0.01); | ||
| }); | ||
|
|
||
| test('handles edge case of exactly 0.01 for minimax', () => { | ||
| expect(clampTemperature('minimax', 0.01)).toBe(0.01); | ||
| }); | ||
|
|
||
| test('handles very small positive values for minimax', () => { | ||
| expect(clampTemperature('minimax', 0.001)).toBe(0.01); | ||
| expect(clampTemperature('minimax', 0.009)).toBe(0.01); | ||
| }); | ||
| }); |
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.
Hyphenate “OpenAI-compatible” for correctness and consistency.
Line 86 should use the compound adjective form.
✏️ Proposed doc fix
🧰 Tools
🪛 LanguageTool
[grammar] ~86-~86: Use a hyphen to join words.
Context: ...,
minimax) -AI_BASE_URL— An OpenAI compatible API endpoint for LLM inferenc...(QB_NEW_EN_HYPHEN)
🤖 Prompt for AI Agents