Skip to content

Commit 780b18c

Browse files
feat(branding): restore upstream providers alongside qBraid defaults
- Change brand.json models from exclusive:true to exclusive:false, default:true so qBraid models are prepended but models.dev catalog and all upstream providers (Anthropic, OpenAI, Copilot, Codex) remain - Add 'default' boolean field to branding ModelsSchema - Update apply.ts models.ts transform: when exclusive=false, prepend branded models and merge with models.dev; only clear CUSTOM_LOADERS and BUILTIN plugin arrays when exclusive=true
1 parent 2a305f1 commit 780b18c

3 files changed

Lines changed: 56 additions & 19 deletions

File tree

branding/apply.ts

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -342,14 +342,14 @@ const PURPLE = RGBA.fromHex("#9370DB")`,
342342
},
343343
},
344344

345-
// Model provider configuration (remove Zen, add qBraid)
346-
// This replaces the entire models.ts to use embedded models
345+
// Model provider configuration
346+
// When exclusive=true: replace get() to return only embedded models (original behavior)
347+
// When default=true, exclusive=false: prepend branded models to the models.dev response
347348
{
348349
pattern: "packages/opencode/src/provider/models.ts",
349350
transform: async (content, config) => {
350-
if (!config.models?.exclusive || !config.models?.source) return content
351+
if (!config.models?.source) return content
351352

352-
// Read the models JSON
353353
const modelsPath = path.join(BRAND_DIR, config.models.source.replace("./", ""))
354354
const modelsFile = Bun.file(modelsPath)
355355
if (!(await modelsFile.exists())) {
@@ -358,16 +358,53 @@ const PURPLE = RGBA.fromHex("#9370DB")`,
358358
}
359359

360360
const modelsJson = await modelsFile.json()
361-
// Remove schema and comment keys
362361
delete modelsJson.$schema
363362
delete modelsJson._comment
364363

365-
// Replace the get() function with one that returns embedded models directly
364+
if (config.models.exclusive) {
365+
// Exclusive mode: only branded models, no models.dev fetch
366+
return content.replace(
367+
/export async function get\(\) \{[\s\S]*?\n \}/,
368+
`export async function get() {
369+
// Branding: embedded models (exclusive mode, no external fetch)
370+
return ${JSON.stringify(modelsJson)} as Record<string, Provider>
371+
}`,
372+
)
373+
}
374+
375+
// Default mode: prepend branded models, then merge models.dev data
376+
// This ensures qBraid models appear first and are the defaults,
377+
// while all upstream providers (Anthropic, OpenAI, Copilot, Codex, etc.)
378+
// remain available.
379+
const brandedModelsStr = JSON.stringify(modelsJson)
366380
return content.replace(
367381
/export async function get\(\) \{[\s\S]*?\n \}/,
368382
`export async function get() {
369-
// Branding: embedded models (no external fetch)
370-
return ${JSON.stringify(modelsJson)} as Record<string, Provider>
383+
// Branding: qBraid models prepended as defaults
384+
const branded = ${brandedModelsStr} as Record<string, Provider>
385+
386+
// Fetch upstream models from models.dev (or cache)
387+
let upstream: Record<string, Provider> = {}
388+
try {
389+
const cached = await readCache()
390+
if (cached) {
391+
upstream = cached
392+
} else {
393+
const response = await fetch(url)
394+
if (response.ok) {
395+
upstream = await response.json() as Record<string, Provider>
396+
await writeCache(upstream)
397+
}
398+
}
399+
} catch (e) {
400+
// Fall back to bundled snapshot if fetch fails
401+
try {
402+
upstream = (await import("./models-snapshot")).default as Record<string, Provider>
403+
} catch {}
404+
}
405+
406+
// Merge: branded providers first, then upstream (branded wins on conflict)
407+
return { ...upstream, ...branded }
371408
}`,
372409
)
373410
},
@@ -386,32 +423,29 @@ const PURPLE = RGBA.fromHex("#9370DB")`,
386423
},
387424
},
388425

389-
// Remove builtin plugins (they don't exist for qBraid branding)
426+
// Remove builtin plugins only in exclusive mode.
427+
// In default mode (exclusive=false), keep plugins so Anthropic auth,
428+
// Codex OAuth, Copilot device code, etc. continue to work.
390429
{
391430
pattern: "packages/opencode/src/plugin/index.ts",
392431
transform: (content, config) => {
393432
if (!config.models?.exclusive) return content
394433

395-
// Clear the BUILTIN array - these npm packages don't exist for branded versions
396-
// Match the array with its contents across potential newlines
397434
return content.replace(
398435
/const BUILTIN = \["[^"]*"(?:,\s*"[^"]*")*\]/,
399436
"const BUILTIN: string[] = [] // Cleared by branding - no external plugins",
400437
)
401438
},
402439
},
403440

404-
// Remove custom loaders for providers that don't exist in exclusive models
441+
// Remove custom loaders only in exclusive mode.
442+
// In default mode, keep all loaders — they're needed for native provider support
443+
// (Anthropic, OpenAI, Bedrock, Copilot, etc.).
405444
{
406445
pattern: "packages/opencode/src/provider/provider.ts",
407446
transform: (content, config) => {
408447
if (!config.models?.exclusive) return content
409448

410-
// Comment out all custom loaders when in exclusive mode
411-
// This prevents "Provider does not exist in model list" errors
412-
// Match the CUSTOM_LOADERS object definition and replace with empty object
413-
// The object starts at "const CUSTOM_LOADERS: Record<string, CustomLoader> = {"
414-
// and ends with " }" before "export const Model"
415449
return content.replace(
416450
/const CUSTOM_LOADERS: Record<string, CustomLoader> = \{[\s\S]*?\n \}(?=\n\n export const Model)/,
417451
`const CUSTOM_LOADERS: Record<string, CustomLoader> = {

branding/qbraid/brand.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
}
3030
},
3131
"models": {
32-
"exclusive": true,
32+
"default": true,
33+
"exclusive": false,
3334
"removeProviders": ["opencode"],
3435
"source": "./models.json"
3536
},

branding/schema.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ export const ModelsSchema = z.object({
4545
.optional(),
4646
/** Provider IDs to completely remove */
4747
removeProviders: z.array(z.string()).optional(),
48-
/** If true, only use the providers defined in this config */
48+
/** If true, only use the providers defined in this config (locks out all others) */
4949
exclusive: z.boolean().optional(),
50+
/** If true, branded models are prepended as defaults but upstream providers remain available */
51+
default: z.boolean().optional(),
5052
})
5153

5254
/**

0 commit comments

Comments
 (0)