diff --git a/ui/desktop/src/components/onboarding/InstitutionalSetupCard.test.tsx b/ui/desktop/src/components/onboarding/InstitutionalSetupCard.test.tsx index 847a9befc..75fe49435 100644 --- a/ui/desktop/src/components/onboarding/InstitutionalSetupCard.test.tsx +++ b/ui/desktop/src/components/onboarding/InstitutionalSetupCard.test.tsx @@ -20,11 +20,27 @@ const PUBLIC_AZURE_KEYS = [ 'AZURE_OPENAI_API_VERSION', ]; -async function connectVersaAzure() { - render(); +/** Types a key, optionally edits Advanced, and waits for the connect to finish. */ +async function connectVersaAzure(advanced: { endpoint?: string; apiVersion?: string } = {}) { + const onSuccess = vi.fn(); + render(); fireEvent.change(screen.getByLabelText(/API Key/i), { target: { value: 'a-key' } }); + if (advanced.endpoint !== undefined || advanced.apiVersion !== undefined) { + fireEvent.click(screen.getByRole('button', { name: /Advanced/i })); + } + if (advanced.endpoint !== undefined) { + fireEvent.change(screen.getByDisplayValue('https://unified-api.ucsf.edu/general'), { + target: { value: advanced.endpoint }, + }); + } + if (advanced.apiVersion !== undefined) { + fireEvent.change(screen.getByDisplayValue('2025-01-01-preview'), { + target: { value: advanced.apiVersion }, + }); + } fireEvent.click(screen.getByRole('button', { name: /Connect to Versa Azure OpenAI/i })); - await waitFor(() => expect(mockCheckProvider).toHaveBeenCalled()); + // Past `checkProvider`, so every write the connect makes has been recorded. + await waitFor(() => expect(onSuccess).toHaveBeenCalledWith('versa_azure')); } async function connectVersaBedrock() { @@ -38,6 +54,8 @@ async function connectVersaBedrock() { await waitFor(() => expect(onSuccess).toHaveBeenCalledWith('versa_bedrock')); } +const writtenKeys = () => mockUpsert.mock.calls.map((c) => c[0] as string); + describe('InstitutionalSetupCard', () => { beforeEach(() => { vi.clearAllMocks(); @@ -52,19 +70,52 @@ describe('InstitutionalSetupCard', () => { // `check_provider_configured` report a Public provider the user never set // up as Configured, one row away in the same grid. await connectVersaAzure(); - const written = mockUpsert.mock.calls.map((c) => c[0] as string); for (const key of PUBLIC_AZURE_KEYS) { - expect(written).not.toContain(key); + expect(writtenKeys()).not.toContain(key); } }); - it('writes the key and the Versa-namespaced overrides', async () => { + it('never writes a deployment: the model a chat picks chooses it', async () => { + // `versa_azure` posts each model to its own deployment. A configured + // `VERSA_AZURE_DEPLOYMENT_NAME` that names a catalog deployment is ignored, + // and any other value pins EVERY model to that one deployment while the + // chat still shows the model it picked. Writing the shipped default was + // the first case on every connect; the box that let a user write anything + // else was the second. await connectVersaAzure(); - const written = mockUpsert.mock.calls.map((c) => c[0] as string); - expect(written).toContain('VERSA_AZURE_API_KEY'); - expect(written).toContain('VERSA_AZURE_ENDPOINT'); - expect(written).toContain('VERSA_AZURE_DEPLOYMENT_NAME'); - expect(written).toContain('VERSA_AZURE_API_VERSION'); + expect(writtenKeys()).not.toContain('VERSA_AZURE_DEPLOYMENT_NAME'); + }); + + it('writes the key, the endpoint and the API version, then selects the provider', async () => { + await connectVersaAzure(); + expect(mockUpsert.mock.calls).toEqual([ + ['VERSA_AZURE_API_KEY', 'a-key', true], + ['VERSA_AZURE_ENDPOINT', 'https://unified-api.ucsf.edu/general', false], + ['VERSA_AZURE_API_VERSION', '2025-01-01-preview', false], + ['BIOROUTER_PROVIDER', 'versa_azure', false], + ]); + }); + + it('writes the endpoint and API version typed into Advanced', async () => { + await connectVersaAzure({ + endpoint: ' https://gateway.example.edu/general ', + apiVersion: '2024-10-21', + }); + expect(mockUpsert).toHaveBeenCalledWith( + 'VERSA_AZURE_ENDPOINT', + 'https://gateway.example.edu/general', + false + ); + expect(mockUpsert).toHaveBeenCalledWith('VERSA_AZURE_API_VERSION', '2024-10-21', false); + }); + + it('offers the endpoint and API version in Advanced, and no deployment', () => { + render(); + fireEvent.click(screen.getByRole('button', { name: /Advanced/i })); + expect(screen.getByText('VERSA_AZURE_ENDPOINT')).toBeInTheDocument(); + expect(screen.getByText('VERSA_AZURE_API_VERSION')).toBeInTheDocument(); + // Neither a field nor a mention in the collapsed label. + expect(screen.queryAllByText(/deployment/i)).toHaveLength(0); }); it('never writes a key in the public AWS namespace when connecting UCSF Versa Bedrock', async () => { diff --git a/ui/desktop/src/components/onboarding/InstitutionalSetupCard.tsx b/ui/desktop/src/components/onboarding/InstitutionalSetupCard.tsx index a297f40be..98863acb2 100644 --- a/ui/desktop/src/components/onboarding/InstitutionalSetupCard.tsx +++ b/ui/desktop/src/components/onboarding/InstitutionalSetupCard.tsx @@ -24,9 +24,16 @@ const VERSA_BEDROCK_DEFAULTS = { VERSA_BEDROCK_REGION: 'us-west-2', }; +// ⚠ No deployment, deliberately. `versa_azure` posts each model to its own +// deployment (`VERSA_AZURE_DEPLOYMENTS` in versa_azure.rs), so the model a chat +// selects chooses it. A `VERSA_AZURE_DEPLOYMENT_NAME` that names a catalog +// deployment is ignored, and any other value pins EVERY model to that one +// deployment while the chat still shows the model it selected. This card used +// to write the shipped default on every connect (ignored) and offer a box for +// any other value (that trap, in a first-run form). An operator who needs a +// deployment the catalog does not list yet sets the key in config instead. const VERSA_AZURE_DEFAULTS = { VERSA_AZURE_ENDPOINT: 'https://unified-api.ucsf.edu/general', - VERSA_AZURE_DEPLOYMENT_NAME: 'gpt-5.5-2026-04-24', VERSA_AZURE_API_VERSION: '2025-01-01-preview', }; @@ -69,9 +76,6 @@ export default function InstitutionalSetupCard({ ); const [bedrockRegion, setBedrockRegion] = useState(VERSA_BEDROCK_DEFAULTS.VERSA_BEDROCK_REGION); const [azureEndpoint, setAzureEndpoint] = useState(VERSA_AZURE_DEFAULTS.VERSA_AZURE_ENDPOINT); - const [azureDeployment, setAzureDeployment] = useState( - VERSA_AZURE_DEFAULTS.VERSA_AZURE_DEPLOYMENT_NAME - ); const [azureApiVersion, setAzureApiVersion] = useState( VERSA_AZURE_DEFAULTS.VERSA_AZURE_API_VERSION ); @@ -103,7 +107,6 @@ export default function InstitutionalSetupCard({ } else { await upsert('VERSA_AZURE_API_KEY', azureApiKey.trim(), true); await upsert('VERSA_AZURE_ENDPOINT', azureEndpoint.trim(), false); - await upsert('VERSA_AZURE_DEPLOYMENT_NAME', azureDeployment.trim(), false); await upsert('VERSA_AZURE_API_VERSION', azureApiVersion.trim(), false); await checkProvider({ body: { provider: 'versa_azure' }, throwOnError: true }); await upsert('BIOROUTER_PROVIDER', 'versa_azure', false); @@ -219,8 +222,7 @@ export default function InstitutionalSetupCard({ {advancedOpen ? '▾' : '▸'} - Advanced ( - {flavor === 'azure' ? 'endpoint, deployment, API version' : 'endpoint, region'}) + Advanced ({flavor === 'azure' ? 'endpoint, API version' : 'endpoint, region'}) @@ -238,18 +240,6 @@ export default function InstitutionalSetupCard({ disabled={isLoading} /> -
- - setAzureDeployment(e.target.value)} - className={advancedInputClass} - disabled={isLoading} - /> -