diff --git a/src/commands/doctor.ts b/src/commands/doctor.ts index 6d6e5c7a8..ee9e78432 100644 --- a/src/commands/doctor.ts +++ b/src/commands/doctor.ts @@ -6102,7 +6102,10 @@ export async function buildChecks( } else { // Live embed test const start = Date.now(); - const vec = await embedOne('gbrain doctor embedding smoke test'); + // Doctor is itself the provider-health circuit breaker. A permanent + // billing/auth failure must be sampled once, not multiplied by the AI + // SDK's default retries (which can add ~90s to every health check). + const vec = await embedOne('gbrain doctor embedding smoke test', { maxRetries: 0 }); const ms = Date.now() - start; const actualDims = vec.length; diff --git a/src/core/ai/gateway.ts b/src/core/ai/gateway.ts index 53d25b34f..bd7df3fba 100644 --- a/src/core/ai/gateway.ts +++ b/src/core/ai/gateway.ts @@ -1951,8 +1951,8 @@ async function embedSubBatch( } /** Embed one text (convenience wrapper). */ -export async function embedOne(text: string): Promise { - const [v] = await embed([text]); +export async function embedOne(text: string, opts?: EmbedOpts): Promise { + const [v] = await embed([text], opts); return v; } diff --git a/test/ai/gateway.test.ts b/test/ai/gateway.test.ts index 7445afb92..801d68585 100644 --- a/test/ai/gateway.test.ts +++ b/test/ai/gateway.test.ts @@ -5,6 +5,8 @@ import { __unconfigureGatewayForTests, isAvailable, embed, + embedOne, + __setEmbedTransportForTests, getEmbeddingModel, getEmbeddingDimensions, getExpansionModel, @@ -17,7 +19,10 @@ import { // (capture / ingest-capture tests), where it produced "Incorrect API key // provided: openai-fake" against the real OpenAI endpoint and wedged // the shard. Reset once at file teardown so no caller sees the residue. -afterAll(() => resetGateway()); +afterAll(() => { + resetGateway(); + __setEmbedTransportForTests(null); +}); import { parseModelId, resolveRecipe } from '../../src/core/ai/model-resolver.ts'; import { dimsProviderOptions, @@ -52,6 +57,35 @@ describe('gateway configuration', () => { }); }); +describe('gateway.embedOne options', () => { + beforeEach(() => { + resetGateway(); + __setEmbedTransportForTests(null); + }); + + test('passes maxRetries=0 to the provider transport for health probes', async () => { + let observedMaxRetries: number | undefined; + configureGateway({ + embedding_model: 'google:gemini-embedding-001', + embedding_dimensions: 3, + env: { GOOGLE_GENERATIVE_AI_API_KEY: 'fake-google' }, + }); + __setEmbedTransportForTests(async (args: any) => { + observedMaxRetries = args.maxRetries; + return { + embeddings: [new Array(3).fill(0.1)], + usage: { tokens: 1 }, + } as any; + }); + + const vector = await embedOne('health probe', { maxRetries: 0 }); + + expect(observedMaxRetries).toBe(0); + expect(vector.length).toBe(3); + __setEmbedTransportForTests(null); + }); +}); + describe('gateway.isAvailable (silent-drop regression surface)', () => { beforeEach(() => resetGateway());