mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
<!-- ccr-slack-attribution --> _Requested by **Saxon Fletcher** · [Slack thread](https://supabase.slack.com/archives/C051L8U2EJF/p1790104861710199?thread_ts=1790104861.710199&cid=C051L8U2EJF)_ ## Problem **Before:** The Assistant's base model is `gpt-5.6-luna` at medium reasoning effort. **After:** The base model is `gpt-6-luna`, its direct successor, still at medium effort. It costs half as much: $0.10/$0.50 per MTok against $0.20/$1.20. Resolves [AI-1245](https://linear.app/supabase/issue/AI-1245/move-the-assistants-base-model-to-gpt-6-luna). ## Solution This swaps `gpt-5.6-luna` for `gpt-6-luna` in `apps/studio/lib/ai/model.utils.ts`: the model ID union, the reasoning-support map, `ASSISTANT_MODELS`, `DEFAULT_ASSISTANT_BASE_MODEL_ID`, and the OpenAI provider registry. The old ID is removed, not kept next to the new one. A stored selection of `gpt-5.6-luna` is no longer a known ID, so the client and `generate-v4` both fall back to the new default. The eval cost table (AI-1242) and eval experiments (AI-1243) are out of scope. Source for the model ID and supported efforts (none/low/medium default/high/xhigh/max): [OpenAI model docs: GPT-6 Luna](https://developers.openai.com/api/docs/models/gpt-6-luna). `@ai-sdk/openai@4.0.41` types model IDs as a union plus `string & {}`, so no SDK bump is needed. ## Review instructions 1. Check the diff in `model.utils.ts`. Say so if you'd rather keep `gpt-5.6-luna` selectable as a fallback. 2. AI-1245 asks for the Assistant evals before shipping. Add the `run-evals` label to run `braintrust-evals.yml` on this PR, or run `pnpm --filter studio evals:run` locally. They have not been run yet because they need OpenAI/Braintrust credentials. 3. Already run: studio `typecheck`, eslint + prettier on the changed files, vitest for `lib/ai`, `pages/api/ai` and `state/ai-assistant` (264 passed), and `evals:preflight`. ## Checklist - [x] I have read [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) - [x] No docs topics changed **AI disclosure:** Claude Code wrote this PR from start to finish. Saxon Fletcher (@SaxonF) is the accountable human and must review it before merge. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01W21zLTfzde6FFPyYGbGC6K --- _Generated by [Claude Code](https://claude.ai/code/session_01W21zLTfzde6FFPyYGbGC6K)_ Co-authored-by: Claude <noreply@anthropic.com>
166 lines
6.3 KiB
TypeScript
166 lines
6.3 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
ASSISTANT_MODELS,
|
|
DEFAULT_ASSISTANT_ADVANCE_MODEL_ID,
|
|
DEFAULT_ASSISTANT_BASE_MODEL_ID,
|
|
DEFAULT_COMPLETION_MODEL,
|
|
defaultAssistantModelId,
|
|
getAssistantModelEntry,
|
|
getDefaultModelForProvider,
|
|
isAdvanceOnlyModelId,
|
|
isAssistantBaseModelId,
|
|
isKnownAssistantModelId,
|
|
openaiModelEntry,
|
|
PROVIDERS,
|
|
} from './model.utils'
|
|
import type { ProviderName } from './model.utils'
|
|
|
|
describe('model.utils', () => {
|
|
describe('getDefaultModelForProvider', () => {
|
|
it('should return correct default for bedrock provider', () => {
|
|
const result = getDefaultModelForProvider('bedrock')
|
|
expect(result).toBe('openai.gpt-oss-120b-1:0')
|
|
})
|
|
|
|
it('should return correct default for openai provider', () => {
|
|
const result = getDefaultModelForProvider('openai')
|
|
expect(result).toBe('gpt-5.4-nano')
|
|
})
|
|
|
|
it('should return undefined for unknown provider', () => {
|
|
const result = getDefaultModelForProvider('unknown' as ProviderName)
|
|
expect(result).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('PROVIDERS registry', () => {
|
|
it('should have bedrock provider with models', () => {
|
|
expect(PROVIDERS.bedrock).toBeDefined()
|
|
expect(PROVIDERS.bedrock.models).toBeDefined()
|
|
expect(Object.keys(PROVIDERS.bedrock.models)).toContain(
|
|
'anthropic.claude-3-7-sonnet-20250219-v1:0'
|
|
)
|
|
expect(Object.keys(PROVIDERS.bedrock.models)).toContain('openai.gpt-oss-120b-1:0')
|
|
})
|
|
|
|
it('should have openai provider with models', () => {
|
|
expect(PROVIDERS.openai).toBeDefined()
|
|
expect(PROVIDERS.openai.models).toBeDefined()
|
|
expect(Object.keys(PROVIDERS.openai.models)).toContain('gpt-6-luna')
|
|
expect(Object.keys(PROVIDERS.openai.models)).toContain('gpt-5.4-nano')
|
|
expect(Object.keys(PROVIDERS.openai.models)).toContain('gpt-5.3-codex')
|
|
})
|
|
|
|
it('should have exactly one default model per provider', () => {
|
|
const providers: ProviderName[] = ['bedrock', 'openai']
|
|
|
|
providers.forEach((provider) => {
|
|
const models = PROVIDERS[provider].models
|
|
const defaultModels = Object.entries(models).filter(([_, config]) => config.default)
|
|
expect(defaultModels.length).toBe(1)
|
|
})
|
|
})
|
|
|
|
it('should have valid model configurations', () => {
|
|
const providers: ProviderName[] = ['bedrock', 'openai']
|
|
|
|
providers.forEach((provider) => {
|
|
const models = PROVIDERS[provider].models
|
|
Object.entries(models).forEach(([_modelId, config]) => {
|
|
expect(config).toHaveProperty('default')
|
|
expect(typeof config.default).toBe('boolean')
|
|
})
|
|
})
|
|
})
|
|
|
|
it('should have bedrock model with systemProviderOptions', () => {
|
|
const sonnetModel = PROVIDERS.bedrock.models['anthropic.claude-3-7-sonnet-20250219-v1:0']
|
|
expect(sonnetModel.systemProviderOptions).toBeDefined()
|
|
expect(sonnetModel.systemProviderOptions?.bedrock).toBeDefined()
|
|
expect(sonnetModel.systemProviderOptions?.bedrock?.cachePoint).toEqual({
|
|
type: 'default',
|
|
})
|
|
})
|
|
|
|
it('should have openai provider with providerOptions', () => {
|
|
expect(PROVIDERS.openai.providerOptions).toBeDefined()
|
|
expect(PROVIDERS.openai.providerOptions?.openai).toBeDefined()
|
|
expect(PROVIDERS.openai.providerOptions?.openai?.reasoningEffort).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('assistant model registry', () => {
|
|
it('should have non-empty base and advance tiers', () => {
|
|
expect(
|
|
ASSISTANT_MODELS.filter((m) => !m.requiresAdvanceModelEntitlement).length
|
|
).toBeGreaterThan(0)
|
|
expect(
|
|
ASSISTANT_MODELS.filter((m) => m.requiresAdvanceModelEntitlement).length
|
|
).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('all model IDs should be unique', () => {
|
|
const ids = ASSISTANT_MODELS.map((m) => m.id)
|
|
expect(new Set(ids).size).toBe(ids.length)
|
|
})
|
|
|
|
it('should have all models in openai provider registry', () => {
|
|
ASSISTANT_MODELS.forEach((entry) => {
|
|
expect(Object.keys(PROVIDERS.openai.models)).toContain(entry.id)
|
|
})
|
|
})
|
|
|
|
it('defaults should satisfy unions', () => {
|
|
expect(DEFAULT_ASSISTANT_BASE_MODEL_ID).toBe('gpt-6-luna')
|
|
expect(DEFAULT_ASSISTANT_ADVANCE_MODEL_ID).toBe('gpt-5.3-codex')
|
|
expect(defaultAssistantModelId(false)).toBe(DEFAULT_ASSISTANT_BASE_MODEL_ID)
|
|
expect(defaultAssistantModelId(true)).toBe(DEFAULT_ASSISTANT_BASE_MODEL_ID)
|
|
})
|
|
|
|
it('isAssistantBaseModelId / isAdvanceOnlyModelId', () => {
|
|
expect(isAssistantBaseModelId('gpt-6-luna')).toBe(true)
|
|
expect(isAssistantBaseModelId('gpt-5.4-nano')).toBe(true)
|
|
expect(isAssistantBaseModelId('gpt-5.3-codex')).toBe(false)
|
|
expect(isAdvanceOnlyModelId('gpt-5.3-codex')).toBe(true)
|
|
expect(isAdvanceOnlyModelId('gpt-5.4-nano')).toBe(false)
|
|
expect(isAdvanceOnlyModelId('gpt-6-luna')).toBe(false)
|
|
})
|
|
|
|
it('isKnownAssistantModelId', () => {
|
|
expect(isKnownAssistantModelId('gpt-6-luna')).toBe(true)
|
|
expect(isKnownAssistantModelId('gpt-5.4-nano')).toBe(true)
|
|
expect(isKnownAssistantModelId('gpt-5.3-codex')).toBe(true)
|
|
expect(isKnownAssistantModelId('gpt-5.6-luna')).toBe(false)
|
|
expect(isKnownAssistantModelId('gpt-5')).toBe(false)
|
|
expect(isKnownAssistantModelId('gpt-5-mini')).toBe(false)
|
|
expect(isKnownAssistantModelId('unknown')).toBe(false)
|
|
})
|
|
|
|
it('getAssistantModelEntry returns config for known ids', () => {
|
|
expect(getAssistantModelEntry('gpt-6-luna').reasoningEffort).toBe('medium')
|
|
expect(getAssistantModelEntry('gpt-5.4-nano').reasoningEffort).toBe('low')
|
|
expect(getAssistantModelEntry('gpt-5.3-codex').reasoningEffort).toBe('low')
|
|
expect(getAssistantModelEntry('gpt-6-luna')).toEqual(
|
|
ASSISTANT_MODELS.find((m) => m.id === 'gpt-6-luna')
|
|
)
|
|
})
|
|
|
|
it('DEFAULT_COMPLETION_MODEL is gpt-5.4-nano with no reasoning effort', () => {
|
|
expect(DEFAULT_COMPLETION_MODEL.id).toBe('gpt-5.4-nano')
|
|
expect(DEFAULT_COMPLETION_MODEL.reasoningEffort).toBe('none')
|
|
})
|
|
|
|
it('openaiModelEntry enforces valid reasoning effort at compile time', () => {
|
|
const withEffort = openaiModelEntry({
|
|
id: 'gpt-6-luna',
|
|
reasoningEffort: 'low',
|
|
})
|
|
expect(withEffort.reasoningEffort).toBe('low')
|
|
|
|
const withoutEffort = openaiModelEntry({ id: 'gpt-6-luna' })
|
|
expect(withoutEffort.reasoningEffort).toBeUndefined()
|
|
})
|
|
})
|
|
})
|