diff --git a/libs/designer-v2/src/lib/core/actions/bjsworkflow/connections.ts b/libs/designer-v2/src/lib/core/actions/bjsworkflow/connections.ts index 678ae125da9..e5e5f7c3d7f 100644 --- a/libs/designer-v2/src/lib/core/actions/bjsworkflow/connections.ts +++ b/libs/designer-v2/src/lib/core/actions/bjsworkflow/connections.ts @@ -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'; @@ -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; + } } } @@ -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( diff --git a/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/custom/useCognitiveService.ts b/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/custom/useCognitiveService.ts index 9ec5a41a4ac..971a337abe5 100644 --- a/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/custom/useCognitiveService.ts +++ b/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/custom/useCognitiveService.ts @@ -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) => { diff --git a/libs/designer-v2/src/lib/ui/panel/connectionsPanel/selectConnection/selectConnection.tsx b/libs/designer-v2/src/lib/ui/panel/connectionsPanel/selectConnection/selectConnection.tsx index cc0c9b443ca..68039d81c1f 100644 --- a/libs/designer-v2/src/lib/ui/panel/connectionsPanel/selectConnection/selectConnection.tsx +++ b/libs/designer-v2/src/lib/ui/panel/connectionsPanel/selectConnection/selectConnection.tsx @@ -23,6 +23,7 @@ import { equals, foundryServiceConnectionRegex, apimanagementRegex, + microsoftFoundryModelsRegex, getIconUriFromConnector, parseErrorMessage, type Connection, @@ -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; @@ -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) @@ -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; }); } diff --git a/libs/designer-v2/src/lib/ui/panel/nodeDetailsPanel/tabs/parametersTab/custom/deploymentModelResource.tsx b/libs/designer-v2/src/lib/ui/panel/nodeDetailsPanel/tabs/parametersTab/custom/deploymentModelResource.tsx index 7eb544f64e2..97115306849 100644 --- a/libs/designer-v2/src/lib/ui/panel/nodeDetailsPanel/tabs/parametersTab/custom/deploymentModelResource.tsx +++ b/libs/designer-v2/src/lib/ui/panel/nodeDetailsPanel/tabs/parametersTab/custom/deploymentModelResource.tsx @@ -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; diff --git a/libs/designer/src/lib/core/actions/bjsworkflow/connections.ts b/libs/designer/src/lib/core/actions/bjsworkflow/connections.ts index a425c9552a1..9287cca9873 100644 --- a/libs/designer/src/lib/core/actions/bjsworkflow/connections.ts +++ b/libs/designer/src/lib/core/actions/bjsworkflow/connections.ts @@ -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'; @@ -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; + } } } @@ -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( diff --git a/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/custom/useCognitiveService.ts b/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/custom/useCognitiveService.ts index 4311aa430a1..c1a1ae98b31 100644 --- a/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/custom/useCognitiveService.ts +++ b/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/custom/useCognitiveService.ts @@ -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) => { diff --git a/libs/designer/src/lib/ui/panel/connectionsPanel/selectConnection/selectConnection.tsx b/libs/designer/src/lib/ui/panel/connectionsPanel/selectConnection/selectConnection.tsx index cc0c9b443ca..68039d81c1f 100644 --- a/libs/designer/src/lib/ui/panel/connectionsPanel/selectConnection/selectConnection.tsx +++ b/libs/designer/src/lib/ui/panel/connectionsPanel/selectConnection/selectConnection.tsx @@ -23,6 +23,7 @@ import { equals, foundryServiceConnectionRegex, apimanagementRegex, + microsoftFoundryModelsRegex, getIconUriFromConnector, parseErrorMessage, type Connection, @@ -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; @@ -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) @@ -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; }); } diff --git a/libs/designer/src/lib/ui/panel/nodeDetailsPanel/tabs/parametersTab/custom/deploymentModelResource.tsx b/libs/designer/src/lib/ui/panel/nodeDetailsPanel/tabs/parametersTab/custom/deploymentModelResource.tsx index 7eb544f64e2..97115306849 100644 --- a/libs/designer/src/lib/ui/panel/nodeDetailsPanel/tabs/parametersTab/custom/deploymentModelResource.tsx +++ b/libs/designer/src/lib/ui/panel/nodeDetailsPanel/tabs/parametersTab/custom/deploymentModelResource.tsx @@ -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; diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/standard/__tests__/connection.spec.ts b/libs/logic-apps-shared/src/designer-client-services/lib/standard/__tests__/connection.spec.ts index 7489546050b..e34fe4bf6c1 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/standard/__tests__/connection.spec.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/standard/__tests__/connection.spec.ts @@ -1,7 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { StandardConnectionService } from '../connection'; +import { StandardConnectionService, microsoftFoundryModelsRegex, foundryServiceConnectionRegex, apimanagementRegex } from '../connection'; import type { StandardConnectionServiceOptions, ConnectionsData } from '../connection'; -import { mcpclientConnectorId } from '../../base/operationmanifest'; +import { agentConnectorId, mcpclientConnectorId } from '../../base/operationmanifest'; import { ConnectionType } from '../../../../utils/src'; import { InitLoggerService } from '../../logger'; @@ -203,4 +203,200 @@ describe('StandardConnectionService', () => { expect(auth.audience).toBe('api://my-app'); }); }); + + describe('createConnection - Agent with /models suffix', () => { + const mockLoggerService = { + log: vi.fn(), + startTrace: vi.fn().mockReturnValue('mock-trace-id'), + endTrace: vi.fn(), + logErrorWithFormatting: vi.fn(), + }; + + const agentConnector = { + id: agentConnectorId, + type: agentConnectorId, + name: 'agent', + properties: { + displayName: 'Agent', + iconUri: '', + brandColor: '#000000', + capabilities: ['actions'], + description: 'Agent', + }, + }; + + it('should append /models to resourceId for standard Cognitive Services connections', async () => { + InitLoggerService([mockLoggerService]); + let capturedConnectionData: any; + const writeConnection = vi.fn().mockImplementation((data: any) => { + capturedConnectionData = data; + return Promise.resolve(); + }); + + const options = createMockOptions({}); + (options as any).writeConnection = writeConnection; + const service = new StandardConnectionService(options); + + const connectionInfo = { + displayName: 'test-agent-foundry', + connectionParametersSet: { + name: 'ManagedServiceIdentity', + values: { + cognitiveServiceAccountId: { + value: '/subscriptions/sub/resourceGroups/rg/providers/Microsoft.CognitiveServices/accounts/myaccount', + }, + }, + }, + }; + + const parametersMetadata = { + connectionMetadata: { type: ConnectionType.Agent }, + }; + + await service.createConnection('test-agent', agentConnector as any, connectionInfo, parametersMetadata as any); + + expect(writeConnection).toHaveBeenCalledOnce(); + expect(capturedConnectionData.connectionData.resourceId).toBe( + '/subscriptions/sub/resourceGroups/rg/providers/Microsoft.CognitiveServices/accounts/myaccount/models' + ); + expect(capturedConnectionData.connectionData.type).toBe('model'); + }); + + it('should NOT append /models for FoundryAgentServiceV2 connections', async () => { + InitLoggerService([mockLoggerService]); + let capturedConnectionData: any; + const writeConnection = vi.fn().mockImplementation((data: any) => { + capturedConnectionData = data; + return Promise.resolve(); + }); + + const options = createMockOptions({}); + (options as any).writeConnection = writeConnection; + const service = new StandardConnectionService(options); + + const foundryResourceId = + '/subscriptions/sub/resourceGroups/rg/providers/Microsoft.CognitiveServices/accounts/myaccount/projects/myproject'; + const connectionInfo = { + displayName: 'test-agent-foundry-service', + connectionParametersSet: { + name: 'ManagedServiceIdentity', + values: { + cognitiveServiceAccountId: { value: foundryResourceId }, + }, + }, + }; + + const parametersMetadata = { + connectionMetadata: { type: ConnectionType.Agent }, + }; + + await service.createConnection('test-agent', agentConnector as any, connectionInfo, parametersMetadata as any); + + expect(writeConnection).toHaveBeenCalledOnce(); + expect(capturedConnectionData.connectionData.resourceId).toBe(foundryResourceId); + expect(capturedConnectionData.connectionData.type).toBe('FoundryAgentServiceV2'); + }); + + it('should NOT append /models for APIM connections', async () => { + InitLoggerService([mockLoggerService]); + let capturedConnectionData: any; + const writeConnection = vi.fn().mockImplementation((data: any) => { + capturedConnectionData = data; + return Promise.resolve(); + }); + + const options = createMockOptions({}); + (options as any).writeConnection = writeConnection; + const service = new StandardConnectionService(options); + + const apimResourceId = '/subscriptions/sub/resourceGroups/rg/providers/Microsoft.ApiManagement/service/myservice/apis/myapi'; + const connectionInfo = { + displayName: 'test-agent-apim', + connectionParametersSet: { + name: 'ManagedServiceIdentity', + values: { + cognitiveServiceAccountId: { value: apimResourceId }, + }, + }, + }; + + const parametersMetadata = { + connectionMetadata: { type: ConnectionType.Agent }, + }; + + await service.createConnection('test-agent', agentConnector as any, connectionInfo, parametersMetadata as any); + + expect(writeConnection).toHaveBeenCalledOnce(); + expect(capturedConnectionData.connectionData.resourceId).toBe(apimResourceId); + expect(capturedConnectionData.connectionData.type).toBe('APIMGenAIGateway'); + }); + }); +}); + +describe('Connection regex patterns', () => { + describe('microsoftFoundryModelsRegex', () => { + it('should match resourceIds ending with /models', () => { + expect( + microsoftFoundryModelsRegex.test( + '/subscriptions/sub/resourceGroups/rg/providers/Microsoft.CognitiveServices/accounts/myaccount/models' + ) + ).toBe(true); + }); + + it('should not match resourceIds without /models suffix', () => { + expect( + microsoftFoundryModelsRegex.test('/subscriptions/sub/resourceGroups/rg/providers/Microsoft.CognitiveServices/accounts/myaccount') + ).toBe(false); + }); + + it('should not match /models in the middle of a path', () => { + expect(microsoftFoundryModelsRegex.test('/some/path/models/deployments')).toBe(false); + }); + + it('should not match FoundryAgentServiceV2 resourceIds', () => { + expect( + microsoftFoundryModelsRegex.test( + '/subscriptions/sub/resourceGroups/rg/providers/Microsoft.CognitiveServices/accounts/myaccount/projects/myproject' + ) + ).toBe(false); + }); + }); + + describe('foundryServiceConnectionRegex', () => { + it('should match FoundryAgentServiceV2 resourceIds with /accounts/x/projects/y', () => { + expect( + foundryServiceConnectionRegex.test( + '/subscriptions/sub/resourceGroups/rg/providers/Microsoft.CognitiveServices/accounts/myaccount/projects/myproject' + ) + ).toBe(true); + }); + + it('should not match standard Cognitive Services resourceIds', () => { + expect( + foundryServiceConnectionRegex.test('/subscriptions/sub/resourceGroups/rg/providers/Microsoft.CognitiveServices/accounts/myaccount') + ).toBe(false); + }); + + it('should not match resourceIds with /models suffix', () => { + expect( + foundryServiceConnectionRegex.test( + '/subscriptions/sub/resourceGroups/rg/providers/Microsoft.CognitiveServices/accounts/myaccount/models' + ) + ).toBe(false); + }); + }); + + describe('apimanagementRegex', () => { + it('should match APIM resourceIds', () => { + expect( + apimanagementRegex.test('/subscriptions/sub/resourceGroups/rg/providers/Microsoft.ApiManagement/service/myservice/apis/myapi') + ).toBe(true); + }); + + it('should not match Cognitive Services resourceIds', () => { + expect(apimanagementRegex.test('/subscriptions/sub/resourceGroups/rg/providers/Microsoft.CognitiveServices/accounts/myaccount')).toBe( + false + ); + }); + }); }); diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/standard/connection.ts b/libs/logic-apps-shared/src/designer-client-services/lib/standard/connection.ts index cea0ed57389..d540523e6f8 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/standard/connection.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/standard/connection.ts @@ -156,6 +156,7 @@ const knowledgeHubLocation = 'knowledgeHubConnections'; const mcpLocation = 'agentMcpConnections'; export const foundryServiceConnectionRegex = /\/Microsoft\.CognitiveServices\/accounts\/[^/]+\/projects\/[^/]+/; export const apimanagementRegex = /\/Microsoft\.ApiManagement\/service\/[^/]+\/apis\/[^/]+/; +export const microsoftFoundryModelsRegex = /\/models$/; export interface StandardConnectionServiceOptions { apiVersion: string; @@ -946,7 +947,11 @@ function convertToAgentConnectionsData( }, endpoint: isBringYourOwnKeyAuth || isClientCertificateAuth ? parameterValues?.['endpoint'] : parameterValues?.['openAIEndpoint'], // Only include resourceId if it has a value - ...(cognitiveServiceAccountId && { resourceId: cognitiveServiceAccountId }), + // Append /models for standard Cognitive Services connections (MicrosoftFoundry) to distinguish from legacy AzureOpenAI + ...(cognitiveServiceAccountId && { + resourceId: + isFoundryAgentServiceConnection || isAPIMManagementConnection ? cognitiveServiceAccountId : `${cognitiveServiceAccountId}/models`, + }), type: isFoundryAgentServiceConnection ? 'FoundryAgentServiceV2' : isAPIMManagementConnection ? 'APIMGenAIGateway' : 'model', }, settings, diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/standard/index.ts b/libs/logic-apps-shared/src/designer-client-services/lib/standard/index.ts index 170dddc24c4..793ae9a9f1a 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/standard/index.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/standard/index.ts @@ -6,6 +6,7 @@ export { escapeSpecialChars, foundryServiceConnectionRegex, apimanagementRegex, + microsoftFoundryModelsRegex, } from './connection'; export { StandardConnectorService, type StandardConnectorServiceOptions } from './connector'; export { StandardOperationManifestService, isServiceProviderOperation } from './operationmanifest'; diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/standard/manifest/__test__/agentloop.spec.ts b/libs/logic-apps-shared/src/designer-client-services/lib/standard/manifest/__test__/agentloop.spec.ts index 5ce821440a7..816f435f1c0 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/standard/manifest/__test__/agentloop.spec.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/standard/manifest/__test__/agentloop.spec.ts @@ -17,13 +17,11 @@ describe('agentloop – Foundry V2 regression', () => { }); it('should contain the full expected set of dropdown values', () => { - expect(dropdownValues).toEqual([ - 'AzureOpenAI', - 'MicrosoftFoundry', - 'FoundryAgentServiceV2', - 'APIMGenAIGateway', - 'V1ChatCompletionsService', - ]); + expect(dropdownValues).toEqual(['MicrosoftFoundry', 'FoundryAgentServiceV2', 'APIMGenAIGateway', 'V1ChatCompletionsService']); + }); + + it('should NOT include AzureOpenAI (removed from new connection creation)', () => { + expect(dropdownValues).not.toContain('AzureOpenAI'); }); it('should label the V2 option as "Foundry project (Preview)"', () => { diff --git a/libs/logic-apps-shared/src/designer-client-services/lib/standard/manifest/agentloop.ts b/libs/logic-apps-shared/src/designer-client-services/lib/standard/manifest/agentloop.ts index 2670577a2d6..79b5b63cc97 100644 --- a/libs/logic-apps-shared/src/designer-client-services/lib/standard/manifest/agentloop.ts +++ b/libs/logic-apps-shared/src/designer-client-services/lib/standard/manifest/agentloop.ts @@ -50,10 +50,6 @@ export default { 'x-ms-editor-options': { readOnly: true, options: [ - { - value: 'AzureOpenAI', - displayName: 'Azure OpenAI', - }, { value: 'MicrosoftFoundry', displayName: 'Foundry Models (Preview)', @@ -74,7 +70,7 @@ export default { ], }, type: 'string', - default: 'AzureOpenAI', + default: 'MicrosoftFoundry', }, foundryAgentName: { type: 'string',