fix(ai): fold dashscope + google keys into gateway env, drop the retired Gemini default (#3500, #3510)

Part A (#3500): dashscope_api_key and google_api_key are now GBrainConfig
file-plane fields folded into the gateway env by buildGatewayConfig
(DASHSCOPE_API_KEY / GOOGLE_GENERATIVE_AI_API_KEY), with process-env
GEMINI_API_KEY accepted as an alias for the canonical Google name.
Honors the deferral note in brain-score-recommendations.ts by adding the
matching HOSTED_EMBED_KEY_CONFIG entries in the same change. A new sweep
guard asserts every KNOWN_CONFIG_KEYS *_api_key field reaches the
gateway env (the #121/#2662/#3500 recurring bug class).

Part B (#3510): google:gemini-1.5-pro (retired by Google) removed from
the google recipe chat allowlist first — so recipe-vs-default guard
tests catch every dead default — then replaced at the default sites:
takes-quality DEFAULT_MODEL_PANEL and grade-takes ensemble docstring use
google:gemini-2.0-flash (and openai:gpt-5.2 for the equally-dead
gpt-4o), the takes-quality pricing allowlist swaps in gemini-2.0-flash +
gpt-5.2, and cross-modal slot C moves to deepseek:deepseek-v4-pro (same
replacement as PR #3501). New test/default-model-panels.test.ts pins the
takes-quality panel to recipe chat lists, canonical pricing, the budget
allowlist, and three distinct providers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-07-28 13:11:24 -07:00
co-authored by Claude Opus 5
parent 6920744dd8
commit 398b17aa30
19 changed files with 263 additions and 44 deletions
+8 -8
View File
@@ -45,7 +45,7 @@ afterEach(() => {
function makeChatStub(scoresBySlot: Record<string, number[]>) {
let callIdx = 0;
const order = ['openai:gpt-4o', 'anthropic:claude-opus-4-7', 'google:gemini-1.5-pro'];
const order = ['openai:gpt-5.2', 'anthropic:claude-opus-4-7', 'deepseek:deepseek-v4-pro'];
return mock(async (opts: { model?: string }) => {
const model = opts.model ?? '';
callIdx++;
@@ -74,9 +74,9 @@ function makeChatStub(scoresBySlot: Record<string, number[]>) {
describe('gbrain eval cross-modal — runner verdict contract', () => {
test('PASS: 3 happy responses, all dims >=7', async () => {
const chatStub = makeChatStub({
'openai:gpt-4o': [9, 8],
'openai:gpt-5.2': [9, 8],
'anthropic:claude-opus-4-7': [8, 7],
'google:gemini-1.5-pro': [8, 8],
'deepseek:deepseek-v4-pro': [8, 8],
});
mock.module('../../src/core/ai/gateway.ts', () => ({
chat: chatStub,
@@ -105,9 +105,9 @@ describe('gbrain eval cross-modal — runner verdict contract', () => {
test('FAIL: one dim mean below 7', async () => {
const chatStub = makeChatStub({
'openai:gpt-4o': [9, 6],
'openai:gpt-5.2': [9, 6],
'anthropic:claude-opus-4-7': [8, 6],
'google:gemini-1.5-pro': [8, 6],
'deepseek:deepseek-v4-pro': [8, 6],
});
mock.module('../../src/core/ai/gateway.ts', () => ({
chat: chatStub,
@@ -130,9 +130,9 @@ describe('gbrain eval cross-modal — runner verdict contract', () => {
test('FAIL: min-score floor caught when one model scores <5 (Q2)', async () => {
const chatStub = makeChatStub({
'openai:gpt-4o': [9, 8],
'openai:gpt-5.2': [9, 8],
'anthropic:claude-opus-4-7': [8, 8],
'google:gemini-1.5-pro': [4, 8], // goal=4 trips the floor
'deepseek:deepseek-v4-pro': [4, 8], // goal=4 trips the floor
});
mock.module('../../src/core/ai/gateway.ts', () => ({
chat: chatStub,
@@ -155,7 +155,7 @@ describe('gbrain eval cross-modal — runner verdict contract', () => {
test('INCONCLUSIVE: 2 of 3 mock 5xx -> exit 2 contract (Q3)', async () => {
const chatStub = mock(async (opts: { model?: string }) => {
if (opts.model === 'openai:gpt-4o') {
if (opts.model === 'openai:gpt-5.2') {
return {
text: JSON.stringify({
scores: { goal: { score: 8 } },