Skip to content
88 changes: 51 additions & 37 deletions libs/designer-v2/src/lib/core/actions/bjsworkflow/connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
LoggerService,
LogEntryLevel,
foundryServiceConnectionRegex,
microsoftFoundryModelsRegex,
} from '@microsoft/logic-apps-shared';
import type { Dispatch } from '@reduxjs/toolkit';
import { createAsyncThunk } from '@reduxjs/toolkit';
Expand Down Expand Up @@ -125,26 +126,26 @@ const updateAgentParametersForConnection = (
// Map display name to manifest parameter value
let agentModelTypeValue = AgentUtils.DisplayNameToManifest[rawModelType] ?? '';

// Fallback: detect Foundry connections by cognitiveServiceAccountId resource pattern
// Fallback: detect connection type by cognitiveServiceAccountId resource pattern
if (!agentModelTypeValue) {
const cognitiveServiceId = connection.properties.connectionParameters?.cognitiveServiceAccountId?.metadata?.value ?? '';
if (foundryServiceConnectionRegex.test(cognitiveServiceId)) {
agentModelTypeValue = 'FoundryAgentServiceV2';
} else if (microsoftFoundryModelsRegex.test(cognitiveServiceId)) {
agentModelTypeValue = 'MicrosoftFoundry';
} else {
// Default to AzureOpenAI, but preserve other existing valid values
// (e.g., 'FoundryAgentServiceV2', 'APIMGenAIGateway') that the regex couldn't detect
agentModelTypeValue = 'AzureOpenAI';
}
}

// If fallback detection yields the default 'AzureOpenAI', preserve existing valid value
// from the workflow definition (e.g., 'MicrosoftFoundry' that shares the same connection pattern)
if (agentModelTypeValue === 'AzureOpenAI') {
const paramGroups = state.operations.inputParameters[nodeId]?.parameterGroups;
const defaultGrp = paramGroups?.[ParameterGroupKeys.DEFAULT];
const existingParam = defaultGrp?.parameters?.find((p) => p.parameterKey === 'inputs.$.agentModelType');
const currentValue = existingParam?.value?.[0]?.value;
const validManifestValues = Object.values(AgentUtils.DisplayNameToManifest);
if (currentValue && validManifestValues.includes(currentValue) && currentValue !== 'AzureOpenAI') {
agentModelTypeValue = currentValue;
const paramGroups = state.operations.inputParameters[nodeId]?.parameterGroups;
const defaultGrp = paramGroups?.[ParameterGroupKeys.DEFAULT];
const existingParam = defaultGrp?.parameters?.find((p) => p.parameterKey === 'inputs.$.agentModelType');
const currentValue = existingParam?.value?.[0]?.value;

const validManifestValues = Object.values(AgentUtils.DisplayNameToManifest);
if (currentValue && validManifestValues.includes(currentValue) && currentValue !== 'AzureOpenAI') {
agentModelTypeValue = currentValue;
}
}
}

Expand Down Expand Up @@ -180,29 +181,42 @@ const updateAgentParametersForConnection = (
},
});

// Update visibility and clear values based on model type
const isV1 = agentModelTypeValue === 'V1ChatCompletionsService';

if (isV1) {
// V1 Chat Completions: show modelId, hide deploymentId
parametersToUpdate.push({
groupId: ParameterGroupKeys.DEFAULT,
parameterId: deploymentIdParam.id,
propertiesToUpdate: {
value: [createLiteralValueSegment('')],
preservedValue: undefined,
},
});
} else {
// AzureOpenAI/Foundry/APIM: show deploymentId, hide modelId
parametersToUpdate.push({
groupId: ParameterGroupKeys.DEFAULT,
parameterId: modelIdParam.id,
propertiesToUpdate: {
value: [createLiteralValueSegment('')],
preservedValue: undefined,
},
});
// Always clear deploymentId and modelId when switching connections
parametersToUpdate.push({
groupId: ParameterGroupKeys.DEFAULT,
parameterId: deploymentIdParam.id,
propertiesToUpdate: {
value: [createLiteralValueSegment('')],
preservedValue: undefined,
},
});
parametersToUpdate.push({
groupId: ParameterGroupKeys.DEFAULT,
parameterId: modelIdParam.id,
propertiesToUpdate: {
value: [createLiteralValueSegment('')],
preservedValue: undefined,
},
});

// Clear deploymentModelProperties (name, format, version) when switching connections
const deploymentModelPropertiesKeys = [
'inputs.$.agentModelSettings.deploymentModelProperties.name',
'inputs.$.agentModelSettings.deploymentModelProperties.format',
'inputs.$.agentModelSettings.deploymentModelProperties.version',
];
for (const key of deploymentModelPropertiesKeys) {
const param = defaultGroup.parameters?.find((p) => p.parameterKey === key);
if (param) {
parametersToUpdate.push({
groupId: ParameterGroupKeys.DEFAULT,
parameterId: param.id,
propertiesToUpdate: {
value: [createLiteralValueSegment('')],
preservedValue: undefined,
},
});
}
}

dispatch(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ const getServiceAccountId = (resourceId: string | undefined, isFoundryServiceCon
return parts.length >= 2 ? parts.slice(0, -2).join('/') : resourceId;
}

return resourceId;
// Strip /models suffix for MicrosoftFoundry connections
return resourceId.replace(/\/models$/, '');
};

export const getCognitiveServiceAccountDeploymentsForConnection = async (connection: Connection) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
equals,
foundryServiceConnectionRegex,
apimanagementRegex,
microsoftFoundryModelsRegex,
getIconUriFromConnector,
parseErrorMessage,
type Connection,
Expand Down Expand Up @@ -64,7 +65,7 @@ export const SelectConnectionWrapper = () => {
if (AgentUtils.isConnector(connector?.id)) {
return connectionData.filter((c) => {
const connectionReference = connectionReferencesForConnector.find((ref) => equals(ref.connection.id, c?.id, true));
let modelType = AgentUtils.ModelType.AzureOpenAI;
let modelType = AgentUtils.ModelType.AzureOpenAI; // default (legacy)
const cognitiveResourceId =
connectionReference?.resourceId ?? c.properties?.connectionParameters?.cognitiveServiceAccountId?.metadata?.value;

Expand All @@ -73,6 +74,8 @@ export const SelectConnectionWrapper = () => {
modelType = AgentUtils.ModelType.FoundryService;
} else if (apimanagementRegex.test(cognitiveResourceId)) {
modelType = AgentUtils.ModelType.APIM;
} else if (microsoftFoundryModelsRegex.test(cognitiveResourceId)) {
modelType = AgentUtils.ModelType.MicrosoftFoundry;
}
} else if (!c.properties?.connectionParameters?.cognitiveServiceAccountId?.metadata?.value) {
// No cognitive serviceAccountId means this is a V1ChatCompletionsService connection (BYO)
Expand All @@ -87,8 +90,12 @@ export const SelectConnectionWrapper = () => {
},
};

// For A2A, hide the foundry connection from the list
return isA2A ? modelType === AgentUtils.ModelType.AzureOpenAI || modelType === AgentUtils.ModelType.V1ChatCompletionsService : true;
// For A2A, show AzureOpenAI, MicrosoftFoundry, and V1 connections only
return isA2A
? modelType === AgentUtils.ModelType.AzureOpenAI ||
modelType === AgentUtils.ModelType.MicrosoftFoundry ||
modelType === AgentUtils.ModelType.V1ChatCompletionsService
: true;
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const CustomDeploymentModelResource = (props: IEditorProps) => {
);

const onSubmit = useCallback(async () => {
const resourceId = metadata?.cognitiveServiceAccountId;
const resourceId = metadata?.cognitiveServiceAccountId?.replace(/\/models$/, '');
if (!resourceId) {
console.error('OpenAI account ID is not provided in metadata.');
return;
Expand Down
88 changes: 51 additions & 37 deletions libs/designer/src/lib/core/actions/bjsworkflow/connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
LoggerService,
LogEntryLevel,
foundryServiceConnectionRegex,
microsoftFoundryModelsRegex,
} from '@microsoft/logic-apps-shared';
import type { Dispatch } from '@reduxjs/toolkit';
import { createAsyncThunk } from '@reduxjs/toolkit';
Expand Down Expand Up @@ -125,26 +126,26 @@ const updateAgentParametersForConnection = (
// Map display name to manifest parameter value
let agentModelTypeValue = AgentUtils.DisplayNameToManifest[rawModelType] ?? '';

// Fallback: detect Foundry connections by cognitiveServiceAccountId resource pattern
// Fallback: detect connection type by cognitiveServiceAccountId resource pattern
if (!agentModelTypeValue) {
const cognitiveServiceId = connection.properties.connectionParameters?.cognitiveServiceAccountId?.metadata?.value ?? '';
if (foundryServiceConnectionRegex.test(cognitiveServiceId)) {
agentModelTypeValue = 'FoundryAgentServiceV2';
} else if (microsoftFoundryModelsRegex.test(cognitiveServiceId)) {
agentModelTypeValue = 'MicrosoftFoundry';
} else {
// Default to AzureOpenAI, but preserve other existing valid values
// (e.g., 'FoundryAgentServiceV2', 'APIMGenAIGateway') that the regex couldn't detect
agentModelTypeValue = 'AzureOpenAI';
}
}

// If fallback detection yields the default 'AzureOpenAI', preserve existing valid value
// from the workflow definition (e.g., 'MicrosoftFoundry' that shares the same connection pattern)
if (agentModelTypeValue === 'AzureOpenAI') {
const paramGroups = state.operations.inputParameters[nodeId]?.parameterGroups;
const defaultGrp = paramGroups?.[ParameterGroupKeys.DEFAULT];
const existingParam = defaultGrp?.parameters?.find((p) => p.parameterKey === 'inputs.$.agentModelType');
const currentValue = existingParam?.value?.[0]?.value;
const validManifestValues = Object.values(AgentUtils.DisplayNameToManifest);
if (currentValue && validManifestValues.includes(currentValue) && currentValue !== 'AzureOpenAI') {
agentModelTypeValue = currentValue;
const paramGroups = state.operations.inputParameters[nodeId]?.parameterGroups;
const defaultGrp = paramGroups?.[ParameterGroupKeys.DEFAULT];
const existingParam = defaultGrp?.parameters?.find((p) => p.parameterKey === 'inputs.$.agentModelType');
const currentValue = existingParam?.value?.[0]?.value;

const validManifestValues = Object.values(AgentUtils.DisplayNameToManifest);
if (currentValue && validManifestValues.includes(currentValue) && currentValue !== 'AzureOpenAI') {
agentModelTypeValue = currentValue;
}
}
}

Expand Down Expand Up @@ -180,29 +181,42 @@ const updateAgentParametersForConnection = (
},
});

// Update visibility and clear values based on model type
const isV1 = agentModelTypeValue === 'V1ChatCompletionsService';

if (isV1) {
// V1 Chat Completions: show modelId, hide deploymentId
parametersToUpdate.push({
groupId: ParameterGroupKeys.DEFAULT,
parameterId: deploymentIdParam.id,
propertiesToUpdate: {
value: [createLiteralValueSegment('')],
preservedValue: undefined,
},
});
} else {
// AzureOpenAI/Foundry/APIM: show deploymentId, hide modelId
parametersToUpdate.push({
groupId: ParameterGroupKeys.DEFAULT,
parameterId: modelIdParam.id,
propertiesToUpdate: {
value: [createLiteralValueSegment('')],
preservedValue: undefined,
},
});
// Always clear deploymentId and modelId when switching connections
parametersToUpdate.push({
groupId: ParameterGroupKeys.DEFAULT,
parameterId: deploymentIdParam.id,
propertiesToUpdate: {
value: [createLiteralValueSegment('')],
preservedValue: undefined,
},
});
parametersToUpdate.push({
groupId: ParameterGroupKeys.DEFAULT,
parameterId: modelIdParam.id,
propertiesToUpdate: {
value: [createLiteralValueSegment('')],
preservedValue: undefined,
},
});

// Clear deploymentModelProperties (name, format, version) when switching connections
const deploymentModelPropertiesKeys = [
'inputs.$.agentModelSettings.deploymentModelProperties.name',
'inputs.$.agentModelSettings.deploymentModelProperties.format',
'inputs.$.agentModelSettings.deploymentModelProperties.version',
];
for (const key of deploymentModelPropertiesKeys) {
const param = defaultGroup.parameters?.find((p) => p.parameterKey === key);
if (param) {
parametersToUpdate.push({
groupId: ParameterGroupKeys.DEFAULT,
parameterId: param.id,
propertiesToUpdate: {
value: [createLiteralValueSegment('')],
preservedValue: undefined,
},
});
}
}

dispatch(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ const getServiceAccountId = (resourceId: string | undefined, isFoundryServiceCon
return parts.length >= 2 ? parts.slice(0, -2).join('/') : resourceId;
}

return resourceId;
// Strip /models suffix for MicrosoftFoundry connections
return resourceId.replace(/\/models$/, '');
};

export const getCognitiveServiceAccountDeploymentsForConnection = async (connection: Connection) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
equals,
foundryServiceConnectionRegex,
apimanagementRegex,
microsoftFoundryModelsRegex,
getIconUriFromConnector,
parseErrorMessage,
type Connection,
Expand Down Expand Up @@ -64,7 +65,7 @@ export const SelectConnectionWrapper = () => {
if (AgentUtils.isConnector(connector?.id)) {
return connectionData.filter((c) => {
const connectionReference = connectionReferencesForConnector.find((ref) => equals(ref.connection.id, c?.id, true));
let modelType = AgentUtils.ModelType.AzureOpenAI;
let modelType = AgentUtils.ModelType.AzureOpenAI; // default (legacy)
const cognitiveResourceId =
connectionReference?.resourceId ?? c.properties?.connectionParameters?.cognitiveServiceAccountId?.metadata?.value;

Expand All @@ -73,6 +74,8 @@ export const SelectConnectionWrapper = () => {
modelType = AgentUtils.ModelType.FoundryService;
} else if (apimanagementRegex.test(cognitiveResourceId)) {
modelType = AgentUtils.ModelType.APIM;
} else if (microsoftFoundryModelsRegex.test(cognitiveResourceId)) {
modelType = AgentUtils.ModelType.MicrosoftFoundry;
}
} else if (!c.properties?.connectionParameters?.cognitiveServiceAccountId?.metadata?.value) {
// No cognitive serviceAccountId means this is a V1ChatCompletionsService connection (BYO)
Expand All @@ -87,8 +90,12 @@ export const SelectConnectionWrapper = () => {
},
};

// For A2A, hide the foundry connection from the list
return isA2A ? modelType === AgentUtils.ModelType.AzureOpenAI || modelType === AgentUtils.ModelType.V1ChatCompletionsService : true;
// For A2A, show AzureOpenAI, MicrosoftFoundry, and V1 connections only
return isA2A
? modelType === AgentUtils.ModelType.AzureOpenAI ||
modelType === AgentUtils.ModelType.MicrosoftFoundry ||
modelType === AgentUtils.ModelType.V1ChatCompletionsService
: true;
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const CustomDeploymentModelResource = (props: IEditorProps) => {
);

const onSubmit = useCallback(async () => {
const resourceId = metadata?.cognitiveServiceAccountId;
const resourceId = metadata?.cognitiveServiceAccountId?.replace(/\/models$/, '');
if (!resourceId) {
console.error('OpenAI account ID is not provided in metadata.');
return;
Expand Down
Loading
Loading