From 02d2200f71a4a027c94f2bcab6bb5a141704cb57 Mon Sep 17 00:00:00 2001 From: Luca Martial Date: Tue, 12 May 2026 22:10:21 -0700 Subject: [PATCH] feat(context): resolve Vertex AI config references --- packages/context/src/llm/local-config.test.ts | 46 +++++++++++++++++++ packages/context/src/llm/local-config.ts | 19 +++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/packages/context/src/llm/local-config.test.ts b/packages/context/src/llm/local-config.test.ts index ffb00b36..5d114e12 100644 --- a/packages/context/src/llm/local-config.test.ts +++ b/packages/context/src/llm/local-config.test.ts @@ -37,6 +37,52 @@ describe('local KTX LLM config', () => { }); }); + it('resolves Vertex AI env references into a KtxLlmConfig', () => { + const config: KtxProjectLlmConfig = { + provider: { + backend: 'vertex', + vertex: { project: 'env:GOOGLE_VERTEX_PROJECT', location: 'env:GOOGLE_VERTEX_LOCATION' }, + }, + models: { default: 'env:KTX_MODEL' }, + promptCaching: { enabled: true, vertexFallbackTo5m: true }, + }; + + expect( + resolveLocalKtxLlmConfig(config, { + GOOGLE_VERTEX_PROJECT: 'local-gcp-project', + GOOGLE_VERTEX_LOCATION: 'us-east5', + KTX_MODEL: 'claude-sonnet-4-6', + }), + ).toEqual({ + backend: 'vertex', + vertex: { project: 'local-gcp-project', location: 'us-east5' }, + modelSlots: { default: 'claude-sonnet-4-6' }, + promptCaching: { enabled: true, vertexFallbackTo5m: true }, + }); + }); + + it('ignores inactive Vertex AI references for non-Vertex backends', () => { + const config: KtxProjectLlmConfig = { + provider: { + backend: 'anthropic', + anthropic: { api_key: 'env:ANTHROPIC_API_KEY' }, // pragma: allowlist secret + vertex: { location: 'env:MISSING_VERTEX_LOCATION' }, + }, + models: { default: 'claude-sonnet-4-6' }, + }; + + expect( + resolveLocalKtxLlmConfig(config, { + ANTHROPIC_API_KEY: 'sk-ant-test', // pragma: allowlist secret + }), + ).toEqual({ + backend: 'anthropic', + anthropic: { apiKey: 'sk-ant-test' }, // pragma: allowlist secret + modelSlots: { default: 'claude-sonnet-4-6' }, + promptCaching: undefined, + }); + }); + it('returns null when the local LLM backend is disabled', () => { expect( createLocalKtxLlmProviderFromConfig({ diff --git a/packages/context/src/llm/local-config.ts b/packages/context/src/llm/local-config.ts index 76f1905f..2709c4b7 100644 --- a/packages/context/src/llm/local-config.ts +++ b/packages/context/src/llm/local-config.ts @@ -67,16 +67,33 @@ function resolvedProviderConfig( }; } +function resolvedVertexConfig( + config: { project?: string; location?: string } | undefined, + env: NodeJS.ProcessEnv, +): { project?: string; location: string } | undefined { + if (!config) { + return undefined; + } + + const project = resolveOptional(config.project, env); + const location = resolveRequired(config.location, env, 'llm.provider.vertex.location is required'); + return { + ...(project ? { project } : {}), + location, + }; +} + export function resolveLocalKtxLlmConfig(config: KtxProjectLlmConfig, env: NodeJS.ProcessEnv): KtxLlmConfig | null { if (config.provider.backend === 'none') { return null; } const modelSlots = resolveModelSlots(config.models, env); + const vertex = config.provider.backend === 'vertex' ? resolvedVertexConfig(config.provider.vertex, env) : undefined; const anthropic = resolvedProviderConfig(config.provider.anthropic, env); const gateway = resolvedProviderConfig(config.provider.gateway, env); return { backend: config.provider.backend, - ...(config.provider.vertex ? { vertex: config.provider.vertex } : {}), + ...(vertex ? { vertex } : {}), ...(anthropic ? { anthropic } : {}), ...(gateway ? { gateway } : {}), modelSlots,