Skip to content

Commit 3c9d278

Browse files
committed
LLMs: +Modular
Add baseline vendor support for Modular models.
1 parent 159faad commit 3c9d278

15 files changed

Lines changed: 331 additions & 6 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as React from 'react';
2+
3+
import { SvgIcon, SvgIconProps } from '@mui/joy';
4+
5+
// Modular brandmark: the stepped 'M' (right stem starts below the apex).
6+
// Official geometry from modular.com's webclip/favicon svg (webflow CDN, 256px artboard), background rect dropped,
7+
// scaled 1/8 and recentered to 24x24, axis-aligned segments compacted to H/V - no shape edits - 2026-08-13
8+
export function ModularIcon(props: SvgIconProps) {
9+
return <SvgIcon viewBox='0 0 24 24' width='24' height='24' fill='currentColor' stroke='none' {...props}>
10+
<path d='M20.885 5.35H23V21.5H20.744V5.486C20.744 5.411 20.68 5.35 20.603 5.35H19.615L13.692 21.5H10.308L4.385 5.35H3.256V21.5H1V2.5H5.654L11.718 19.057H12.282L18.346 2.5H20.744V5.214C20.744 5.289 20.807 5.35 20.885 5.35Z' />
11+
</SvgIcon>;
12+
}

src/common/gen/com.site.docs.slug.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type SiteDocSlug =
2020
| 'connect-lmstudio'
2121
| 'connect-localai'
2222
| 'connect-mistral'
23+
| 'connect-modular'
2324
| 'connect-moonshot-ai'
2425
| 'connect-nvidia-nim'
2526
| 'connect-ollama'

src/common/gen/com.site.pages.slug.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export type SitePageSlug =
2222
| 'minimax'
2323
| 'mistral'
2424
| 'models'
25+
| 'modular'
2526
| 'moonshot'
2627
| 'nvidia'
2728
| 'ollama'

src/modules/aix/server/dispatch/chatGenerate/chatGenerate.dispatch.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ export async function createChatGenerateDispatch(access: AixAPI_Access, model: A
248248
case 'lmstudio':
249249
case 'localai':
250250
case 'mistral':
251+
case 'modular':
251252
case 'moonshot':
252253
case 'nvidianim':
253254
case 'openai':
@@ -369,6 +370,7 @@ export async function createChatGenerateResumeDispatch(access: AixAPI_Access, re
369370
case 'lmstudio':
370371
case 'localai':
371372
case 'mistral':
373+
case 'modular':
372374
case 'moonshot':
373375
case 'nvidianim':
374376
case 'ollama':

src/modules/llms/components/LLMVendorIcon.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { GroqIcon } from '~/common/components/icons/vendors/GroqIcon';
2121
import { LMStudioIcon } from '~/common/components/icons/vendors/LMStudioIcon';
2222
import { LocalAIIcon } from '~/common/components/icons/vendors/LocalAIIcon';
2323
import { MistralIcon } from '~/common/components/icons/vendors/MistralIcon';
24+
import { ModularIcon } from '~/common/components/icons/vendors/ModularIcon';
2425
import { MoonshotIcon } from '~/common/components/icons/vendors/MoonshotIcon';
2526
import { NvidiaIcon } from '~/common/components/icons/vendors/NvidiaIcon';
2627
import { OllamaIcon } from '~/common/components/icons/vendors/OllamaIcon';
@@ -51,6 +52,7 @@ const vendorIcons: Record<ModelVendorId, React.FunctionComponent<SvgIconProps>>
5152
lmstudio: LMStudioIcon,
5253
localai: LocalAIIcon,
5354
mistral: MistralIcon,
55+
modular: ModularIcon,
5456
moonshot: MoonshotIcon,
5557
nvidianim: NvidiaIcon,
5658
ollama: OllamaIcon,

src/modules/llms/components/LLMVendorIconSprite.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const VI: Record<ModelVendorId, string> = {
2424
lmstudio: 'vi-lmstudio',
2525
localai: 'vi-localai',
2626
mistral: 'vi-mistral',
27+
modular: 'vi-modular',
2728
moonshot: 'vi-moonshot',
2829
nvidianim: 'vi-nvidianim',
2930
ollama: 'vi-ollama',
@@ -136,6 +137,12 @@ export const VendorIconSpriteMemo = React.memo(function VendorIconSprite() {
136137
</g>
137138
</symbol>
138139

140+
<symbol id={VI.modular} viewBox='0 0 24 24'>
141+
<g fill='currentColor' stroke='none'>
142+
<path d='M20.885 5.35H23V21.5H20.744V5.486C20.744 5.411 20.68 5.35 20.603 5.35H19.615L13.692 21.5H10.308L4.385 5.35H3.256V21.5H1V2.5H5.654L11.718 19.057H12.282L18.346 2.5H20.744V5.214C20.744 5.289 20.807 5.35 20.885 5.35Z' />
143+
</g>
144+
</symbol>
145+
139146
<symbol id={VI.moonshot} viewBox='0 0 465 470'>
140147
<g strokeWidth={0} stroke='none' fill='currentColor' strokeLinecap='butt' strokeLinejoin='miter'>
141148
<path fillRule='evenodd' clipRule='evenodd'

src/modules/llms/components/LLMVendorSetup.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { GroqServiceSetup } from '../vendors/groq/GroqServiceSetup';
1919
import { LMStudioServiceSetup } from '../vendors/lmstudio/LMStudioServiceSetup';
2020
import { LocalAIServiceSetup } from '../vendors/localai/LocalAIServiceSetup';
2121
import { MistralServiceSetup } from '../vendors/mistral/MistralServiceSetup';
22+
import { ModularServiceSetup } from '../vendors/modular/ModularServiceSetup';
2223
import { MoonshotServiceSetup } from '../vendors/moonshot/MoonshotServiceSetup';
2324
import { NvidiaNIMServiceSetup } from '../vendors/nvidianim/NvidiaNIMServiceSetup';
2425
import { OllamaServiceSetup } from '../vendors/ollama/OllamaServiceSetup';
@@ -49,6 +50,7 @@ const vendorSetupComponents: Record<ModelVendorId, React.ComponentType<{ service
4950
lmstudio: LMStudioServiceSetup,
5051
localai: LocalAIServiceSetup,
5152
mistral: MistralServiceSetup,
53+
modular: ModularServiceSetup,
5254
moonshot: MoonshotServiceSetup,
5355
nvidianim: NvidiaNIMServiceSetup,
5456
ollama: OllamaServiceSetup,
@@ -63,9 +65,8 @@ const vendorSetupComponents: Record<ModelVendorId, React.ComponentType<{ service
6365

6466

6567
/**
66-
* Vendor -> big-agi.com/docs setup page. Written out because five ids differ from their slug
67-
* (googleai, moonshot, nvidianim, sakanaai, togetherai); Record<ModelVendorId, ...> is exhaustive,
68-
* so a new vendor cannot be registered without a docs slug (build-checked via SiteDocSlug).
68+
* Vendor -> big-agi.com/docs setup page. Written out because some ids differ from their slug
69+
* Record<ModelVendorId, ...> is exhaustive so a new vendor cannot be registered without a docs slug (build-checked via SiteDocSlug).
6970
*/
7071
export const VENDOR_DOCS: Record<ModelVendorId, SiteDocSlug> = {
7172
alibaba: 'connect-alibaba',
@@ -80,6 +81,7 @@ export const VENDOR_DOCS: Record<ModelVendorId, SiteDocSlug> = {
8081
lmstudio: 'connect-lmstudio',
8182
localai: 'connect-localai',
8283
mistral: 'connect-mistral',
84+
modular: 'connect-modular',
8385
moonshot: 'connect-moonshot-ai',
8486
nvidianim: 'connect-nvidia-nim',
8587
ollama: 'connect-ollama',

src/modules/llms/server/listModels.dispatch.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import { nvidiaNIMHeuristic, nvidiaNIMModelsToModelDescriptions } from './openai
5151
import { lmStudioFetchModels, lmStudioModelsToModelDescriptions } from './openai/models/lmstudio.models';
5252
import { localAIModelSortFn, localAIModelToModelDescription } from './openai/models/localai.models';
5353
import { mistralModels } from './openai/models/mistral.models';
54+
import { modularModelsToModelDescriptions } from './openai/models/modular.models';
5455
import { moonshotModelFilter, moonshotModelSortFn, moonshotModelToModelDescription } from './openai/models/moonshot.models';
5556
import { openRouterInjectVariants, openRouterModelFamilySortFn, openRouterModelToModelDescription } from './openai/models/openrouter.models';
5657
import { openAIInjectVariants, openAIModelFilter, openAIModelToModelDescription, openAISortModels, openaiValidateModelDefs_DEV } from './openai/models/openai.models';
@@ -387,6 +388,7 @@ function _listModelsCreateDispatch(access: AixAPI_Access, signal?: AbortSignal):
387388
case 'groq':
388389
case 'localai':
389390
case 'mistral':
391+
case 'modular':
390392
case 'moonshot':
391393
case 'nvidianim':
392394
case 'openai':
@@ -489,6 +491,10 @@ function _listModelsCreateDispatch(access: AixAPI_Access, signal?: AbortSignal):
489491
case 'mistral':
490492
return mistralModels(maybeModels);
491493

494+
case 'modular':
495+
// [Modular] API lists ids only; caps/pricing from manual mappings, unknown ids kept (self-hosted MAX serves anything)
496+
return modularModelsToModelDescriptions(maybeModels);
497+
492498
case 'moonshot':
493499
return maybeModels
494500
.filter(moonshotModelFilter)

src/modules/llms/server/listModels.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,13 @@ describe('listModels enumeration', () => {
264264
);
265265
});
266266

267+
test('openai-compat/modular: live listing', { skip: skipIfMissing('MODULAR_API_KEY') }, async () => {
268+
await expectOk(
269+
{ dialect: 'modular', ...openAIShape({ oaiKey: E.MODULAR_API_KEY || '' }) } as AixAPI_Access,
270+
1, 'modular/live',
271+
);
272+
});
273+
267274
test('openai-compat/moonshot: live listing', { skip: skipIfMissing('MOONSHOT_API_KEY') }, async () => {
268275
await expectOk(
269276
{ dialect: 'moonshot', ...openAIShape({ oaiKey: E.MOONSHOT_API_KEY || '' }) } as AixAPI_Access,
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import * as z from 'zod/v4';
2+
3+
import { LLM_IF_OAI_Chat, LLM_IF_OAI_Fn, LLM_IF_OAI_Json, LLM_IF_OAI_PromptCaching, LLM_IF_OAI_Reasoning, LLM_IF_OAI_Vision } from '~/common/stores/llms/llms.types';
4+
5+
import { serverCapitalizeFirstLetter } from '~/server/wire';
6+
7+
import type { ModelDescriptionSchema } from '../../llm.server.types';
8+
9+
import { fromManualMapping, llmsDefineManualMappings } from '../../models.mappings';
10+
11+
// --- Modular Model ID inference (auto-derived from _modularKnownModels) ---
12+
export type LlmsModularModelId = typeof _modularKnownModels[number]['idPrefix'];
13+
14+
15+
// [Modular] Models List API schema - observed at https://api.modular.com/v1/models (2026-08-13).
16+
// The list carries only id/object/created/owned_by - no capabilities, no pricing - so all caps and
17+
// prices come from the manual mappings below. Ids mostly mirror HuggingFace repo names, are
18+
// CASE-SENSITIVE, and churn without notice (MiniMaxAI/MiniMax-M3 -> minimax/minimax-m3 mid-day
19+
// 2026-08-13, the old id now 404s) - keep the table in sync with the live list.
20+
const _wireModularModelItemSchema = z.object({
21+
id: z.string(), // only strictly required field
22+
object: z.string().nullish(),
23+
created: z.number().nullish(),
24+
owned_by: z.string().nullish(),
25+
});
26+
27+
28+
// [Modular Cloud] Editorial table for the shared endpoints (array order = display order), measured
29+
// live 2026-08-13. Output caps are unverified where noted: the server silently clamps oversized
30+
// max_tokens instead of erroring, so an over-large value is never observable as a failure.
31+
const _modularKnownModels = llmsDefineManualMappings([
32+
{
33+
idPrefix: 'minimax/minimax-m3',
34+
label: 'MiniMax M3',
35+
description: '1M-context multimodal MoE with default-on reasoning. Served as NVIDIA NVFP4 (4-bit) quantization on Modular Cloud shared endpoints.',
36+
contextWindow: 1048576,
37+
maxCompletionTokens: 131072, // unverified
38+
interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Fn, LLM_IF_OAI_Vision, LLM_IF_OAI_Reasoning, LLM_IF_OAI_PromptCaching],
39+
chatPrice: { input: 0.30, output: 1.20, cache: { cType: 'oai-ac', read: 0.06 } },
40+
},
41+
{
42+
idPrefix: 'google/gemma-4-31b-it',
43+
label: 'Gemma 4 31B',
44+
description: 'Google Gemma 4 31B instruction-tuned, text+image input. Served as NVIDIA NVFP4 (4-bit) quantization.',
45+
contextWindow: 262144,
46+
maxCompletionTokens: 32768, // unverified
47+
// no Reasoning (the catalog claims it, but this deployment exposes no reasoning surface) and no Json
48+
// (json_object emits type-corrupted output here)
49+
interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Fn, LLM_IF_OAI_Vision],
50+
chatPrice: { input: 0.25, output: 0.65 },
51+
},
52+
{
53+
idPrefix: 'google/gemma-4-26b-a4b-it',
54+
label: 'Gemma 4 26B A4B',
55+
description: 'Google Gemma 4 26B MoE (4B active), text+image input. Served as NVIDIA NVFP4 (4-bit) quantization.',
56+
contextWindow: 262144,
57+
maxCompletionTokens: 32768, // unverified
58+
interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Fn, LLM_IF_OAI_Vision, LLM_IF_OAI_Json],
59+
chatPrice: { input: 0.15, output: 0.60 },
60+
},
61+
{
62+
idPrefix: 'moonshotai/kimi-k2.7-code',
63+
label: 'Kimi K2.7 Code',
64+
description: 'Moonshot Kimi K2.7 Code, agentic coding model with always-on reasoning. Served as NVIDIA NVFP4 (4-bit) quantization.',
65+
contextWindow: 262144,
66+
maxCompletionTokens: 131072, // unverified
67+
interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Fn, LLM_IF_OAI_Vision, LLM_IF_OAI_Reasoning, LLM_IF_OAI_Json, LLM_IF_OAI_PromptCaching],
68+
chatPrice: { input: 0.60, output: 3.00, cache: { cType: 'oai-ac', read: 0.12 } },
69+
},
70+
]);
71+
72+
73+
function _prettyModelId(id: string): string {
74+
// fallback labeler for unknown models, e.g. "deepseek-ai/DeepSeek-V3" => "DeepSeek V3"
75+
return (id.split('/').pop() || id)
76+
.replaceAll(/[_-]/g, ' ')
77+
.split(' ')
78+
.map(serverCapitalizeFirstLetter)
79+
.join(' ')
80+
.trim();
81+
}
82+
83+
84+
export function modularModelsToModelDescriptions(wireModels: unknown): ModelDescriptionSchema[] {
85+
86+
// tolerant top-level unwrap: accept a plain array or `{ data: [...] }`, else fall back to []
87+
let rawItems: unknown[] = [];
88+
if (Array.isArray(wireModels))
89+
rawItems = wireModels;
90+
else if (wireModels && typeof wireModels === 'object' && Array.isArray((wireModels as { data?: unknown[] }).data))
91+
rawItems = (wireModels as { data: unknown[] }).data;
92+
93+
const descriptions: ModelDescriptionSchema[] = [];
94+
95+
for (const rawItem of rawItems) {
96+
// per-item safeParse: one bad entry never crashes the rest
97+
const { data: model, error } = _wireModularModelItemSchema.safeParse(rawItem);
98+
if (error || !model?.id) {
99+
if (error) console.warn('[DEV] modular: skipping invalid model entry', z.prettifyError(error));
100+
continue;
101+
}
102+
103+
// known models get full caps/pricing; unknown ids (day-zero cloud additions, or any model on a
104+
// self-hosted MAX host) stay visible with a conservative chat-only shape and no context window
105+
descriptions.push(fromManualMapping(_modularKnownModels, model.id, model.created ?? undefined, undefined, {
106+
idPrefix: model.id,
107+
label: _prettyModelId(model.id),
108+
description: 'Model served via Modular.',
109+
contextWindow: null,
110+
interfaces: [LLM_IF_OAI_Chat, LLM_IF_OAI_Fn],
111+
hidden: false,
112+
}));
113+
}
114+
115+
// sort into editorial display order (= _modularKnownModels array order; unknown models sort at their
116+
// family slot via prefix else last, ties by id)
117+
const _rank = (id: string) => {
118+
const exact = _modularKnownModels.findIndex(known => id === known.idPrefix);
119+
if (exact !== -1) return exact;
120+
const prefix = _modularKnownModels.findIndex(known => id.startsWith(known.idPrefix));
121+
return prefix === -1 ? _modularKnownModels.length : prefix;
122+
};
123+
return descriptions.sort((a, b) => _rank(a.id) - _rank(b.id) || a.id.localeCompare(b.id));
124+
}

0 commit comments

Comments
 (0)