Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,27 @@ const PUBLIC_AZURE_KEYS = [
'AZURE_OPENAI_API_VERSION',
];

async function connectVersaAzure() {
render(<InstitutionalSetupCard onSuccess={vi.fn()} />);
/** 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(<InstitutionalSetupCard onSuccess={onSuccess} />);
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() {
Expand All @@ -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();
Expand All @@ -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(<InstitutionalSetupCard onSuccess={vi.fn()} />);
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 () => {
Expand Down
28 changes: 9 additions & 19 deletions ui/desktop/src/components/onboarding/InstitutionalSetupCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
};

Expand Down Expand Up @@ -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
);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -219,8 +222,7 @@ export default function InstitutionalSetupCard({
<CollapsibleTrigger className="flex items-center gap-1.5 text-xs text-text-muted hover:text-text-default transition-colors duration-150">
<span>{advancedOpen ? '▾' : '▸'}</span>
<span>
Advanced (
{flavor === 'azure' ? 'endpoint, deployment, API version' : 'endpoint, region'})
Advanced ({flavor === 'azure' ? 'endpoint, API version' : 'endpoint, region'})
</span>
</CollapsibleTrigger>
<CollapsibleContent className="mt-2.5 pl-3.5 space-y-2.5 border-l border-border-default">
Expand All @@ -238,18 +240,6 @@ export default function InstitutionalSetupCard({
disabled={isLoading}
/>
</div>
<div>
<label className="block text-[11px] text-text-muted mb-1">
VERSA_AZURE_DEPLOYMENT_NAME
</label>
<input
type="text"
value={azureDeployment}
onChange={(e) => setAzureDeployment(e.target.value)}
className={advancedInputClass}
disabled={isLoading}
/>
</div>
<div>
<label className="block text-[11px] text-text-muted mb-1">
VERSA_AZURE_API_VERSION
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ interface DefaultProviderSetupFormProps {
// Frontend-side defaults per provider — ensures defaults show up immediately
// without requiring a backend recompile. The backend also declares these defaults
// in Rust (azure.rs, bedrock.rs) for CLI consistency.
//
// ⚠ An entry only reaches a key the provider DECLARES in `metadata.config_keys`:
// both readers below look a default up per declared parameter, so one for any
// other key is never read. `versa_azure` and `versa_bedrock` declare only their
// credentials, which is why neither has an entry.
const PROVIDER_KEY_DEFAULTS: Record<string, Record<string, string>> = {
azure_openai: {
AZURE_OPENAI_ENDPOINT: 'https://unified-api.ucsf.edu/general',
Expand All @@ -30,11 +35,6 @@ const PROVIDER_KEY_DEFAULTS: Record<string, Record<string, string>> = {
aws_bedrock: {
AWS_REGION: 'us-west-2',
},
versa_azure: {
AZURE_OPENAI_ENDPOINT: 'https://unified-api.ucsf.edu/general',
AZURE_OPENAI_DEPLOYMENT_NAME: 'gpt-5.5-2026-04-24',
AZURE_OPENAI_API_VERSION: '2025-01-01-preview',
},
};

const envToPrettyName = (envVar: string) => {
Expand Down
Loading