diff --git a/docs-builder.sln b/docs-builder.sln index 559deb139..055eccce7 100644 --- a/docs-builder.sln +++ b/docs-builder.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Markdown", "src\Elastic.Markdown\Elastic.Markdown.csproj", "{4D198E25-C211-41DC-9E84-B15E89BD7048}" EndProject @@ -145,142 +145,387 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation.Links EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation.Isolated", "src\services\Elastic.Documentation.Isolated\Elastic.Documentation.Isolated.csproj", "{AABD3EF7-8C86-4981-B1D2-B1F786F33069}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation.Api.Infrastructure.Tests", "tests\Elastic.Documentation.Api.Infrastructure.Tests\Elastic.Documentation.Api.Infrastructure.Tests.csproj", "{77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {4D198E25-C211-41DC-9E84-B15E89BD7048}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4D198E25-C211-41DC-9E84-B15E89BD7048}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D198E25-C211-41DC-9E84-B15E89BD7048}.Debug|x64.ActiveCfg = Debug|Any CPU + {4D198E25-C211-41DC-9E84-B15E89BD7048}.Debug|x64.Build.0 = Debug|Any CPU + {4D198E25-C211-41DC-9E84-B15E89BD7048}.Debug|x86.ActiveCfg = Debug|Any CPU + {4D198E25-C211-41DC-9E84-B15E89BD7048}.Debug|x86.Build.0 = Debug|Any CPU {4D198E25-C211-41DC-9E84-B15E89BD7048}.Release|Any CPU.ActiveCfg = Release|Any CPU {4D198E25-C211-41DC-9E84-B15E89BD7048}.Release|Any CPU.Build.0 = Release|Any CPU + {4D198E25-C211-41DC-9E84-B15E89BD7048}.Release|x64.ActiveCfg = Release|Any CPU + {4D198E25-C211-41DC-9E84-B15E89BD7048}.Release|x64.Build.0 = Release|Any CPU + {4D198E25-C211-41DC-9E84-B15E89BD7048}.Release|x86.ActiveCfg = Release|Any CPU + {4D198E25-C211-41DC-9E84-B15E89BD7048}.Release|x86.Build.0 = Release|Any CPU {13387C19-03EC-41AC-A0FC-B5A7E3761DA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {13387C19-03EC-41AC-A0FC-B5A7E3761DA6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {13387C19-03EC-41AC-A0FC-B5A7E3761DA6}.Debug|x64.ActiveCfg = Debug|Any CPU + {13387C19-03EC-41AC-A0FC-B5A7E3761DA6}.Debug|x64.Build.0 = Debug|Any CPU + {13387C19-03EC-41AC-A0FC-B5A7E3761DA6}.Debug|x86.ActiveCfg = Debug|Any CPU + {13387C19-03EC-41AC-A0FC-B5A7E3761DA6}.Debug|x86.Build.0 = Debug|Any CPU {13387C19-03EC-41AC-A0FC-B5A7E3761DA6}.Release|Any CPU.ActiveCfg = Release|Any CPU {13387C19-03EC-41AC-A0FC-B5A7E3761DA6}.Release|Any CPU.Build.0 = Release|Any CPU + {13387C19-03EC-41AC-A0FC-B5A7E3761DA6}.Release|x64.ActiveCfg = Release|Any CPU + {13387C19-03EC-41AC-A0FC-B5A7E3761DA6}.Release|x64.Build.0 = Release|Any CPU + {13387C19-03EC-41AC-A0FC-B5A7E3761DA6}.Release|x86.ActiveCfg = Release|Any CPU + {13387C19-03EC-41AC-A0FC-B5A7E3761DA6}.Release|x86.Build.0 = Release|Any CPU {01F05AD0-E0E0-401F-A7EC-905928E1E9F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {01F05AD0-E0E0-401F-A7EC-905928E1E9F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01F05AD0-E0E0-401F-A7EC-905928E1E9F0}.Debug|x64.ActiveCfg = Debug|Any CPU + {01F05AD0-E0E0-401F-A7EC-905928E1E9F0}.Debug|x64.Build.0 = Debug|Any CPU + {01F05AD0-E0E0-401F-A7EC-905928E1E9F0}.Debug|x86.ActiveCfg = Debug|Any CPU + {01F05AD0-E0E0-401F-A7EC-905928E1E9F0}.Debug|x86.Build.0 = Debug|Any CPU {01F05AD0-E0E0-401F-A7EC-905928E1E9F0}.Release|Any CPU.ActiveCfg = Release|Any CPU {01F05AD0-E0E0-401F-A7EC-905928E1E9F0}.Release|Any CPU.Build.0 = Release|Any CPU + {01F05AD0-E0E0-401F-A7EC-905928E1E9F0}.Release|x64.ActiveCfg = Release|Any CPU + {01F05AD0-E0E0-401F-A7EC-905928E1E9F0}.Release|x64.Build.0 = Release|Any CPU + {01F05AD0-E0E0-401F-A7EC-905928E1E9F0}.Release|x86.ActiveCfg = Release|Any CPU + {01F05AD0-E0E0-401F-A7EC-905928E1E9F0}.Release|x86.Build.0 = Release|Any CPU {1A8659C1-222A-4824-B562-ED8F88658C05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1A8659C1-222A-4824-B562-ED8F88658C05}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A8659C1-222A-4824-B562-ED8F88658C05}.Debug|x64.ActiveCfg = Debug|Any CPU + {1A8659C1-222A-4824-B562-ED8F88658C05}.Debug|x64.Build.0 = Debug|Any CPU + {1A8659C1-222A-4824-B562-ED8F88658C05}.Debug|x86.ActiveCfg = Debug|Any CPU + {1A8659C1-222A-4824-B562-ED8F88658C05}.Debug|x86.Build.0 = Debug|Any CPU {1A8659C1-222A-4824-B562-ED8F88658C05}.Release|Any CPU.ActiveCfg = Release|Any CPU {1A8659C1-222A-4824-B562-ED8F88658C05}.Release|Any CPU.Build.0 = Release|Any CPU + {1A8659C1-222A-4824-B562-ED8F88658C05}.Release|x64.ActiveCfg = Release|Any CPU + {1A8659C1-222A-4824-B562-ED8F88658C05}.Release|x64.Build.0 = Release|Any CPU + {1A8659C1-222A-4824-B562-ED8F88658C05}.Release|x86.ActiveCfg = Release|Any CPU + {1A8659C1-222A-4824-B562-ED8F88658C05}.Release|x86.Build.0 = Release|Any CPU {B27C5107-128B-465A-B8F8-8985399E4CFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B27C5107-128B-465A-B8F8-8985399E4CFB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B27C5107-128B-465A-B8F8-8985399E4CFB}.Debug|x64.ActiveCfg = Debug|Any CPU + {B27C5107-128B-465A-B8F8-8985399E4CFB}.Debug|x64.Build.0 = Debug|Any CPU + {B27C5107-128B-465A-B8F8-8985399E4CFB}.Debug|x86.ActiveCfg = Debug|Any CPU + {B27C5107-128B-465A-B8F8-8985399E4CFB}.Debug|x86.Build.0 = Debug|Any CPU {B27C5107-128B-465A-B8F8-8985399E4CFB}.Release|Any CPU.ActiveCfg = Release|Any CPU {B27C5107-128B-465A-B8F8-8985399E4CFB}.Release|Any CPU.Build.0 = Release|Any CPU + {B27C5107-128B-465A-B8F8-8985399E4CFB}.Release|x64.ActiveCfg = Release|Any CPU + {B27C5107-128B-465A-B8F8-8985399E4CFB}.Release|x64.Build.0 = Release|Any CPU + {B27C5107-128B-465A-B8F8-8985399E4CFB}.Release|x86.ActiveCfg = Release|Any CPU + {B27C5107-128B-465A-B8F8-8985399E4CFB}.Release|x86.Build.0 = Release|Any CPU {10857974-6CF1-42B5-B793-AAA988BD7348}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {10857974-6CF1-42B5-B793-AAA988BD7348}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10857974-6CF1-42B5-B793-AAA988BD7348}.Debug|x64.ActiveCfg = Debug|Any CPU + {10857974-6CF1-42B5-B793-AAA988BD7348}.Debug|x64.Build.0 = Debug|Any CPU + {10857974-6CF1-42B5-B793-AAA988BD7348}.Debug|x86.ActiveCfg = Debug|Any CPU + {10857974-6CF1-42B5-B793-AAA988BD7348}.Debug|x86.Build.0 = Debug|Any CPU {10857974-6CF1-42B5-B793-AAA988BD7348}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10857974-6CF1-42B5-B793-AAA988BD7348}.Release|x64.ActiveCfg = Release|Any CPU + {10857974-6CF1-42B5-B793-AAA988BD7348}.Release|x64.Build.0 = Release|Any CPU + {10857974-6CF1-42B5-B793-AAA988BD7348}.Release|x86.ActiveCfg = Release|Any CPU + {10857974-6CF1-42B5-B793-AAA988BD7348}.Release|x86.Build.0 = Release|Any CPU {018F959E-824B-4664-B345-066784478D24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {018F959E-824B-4664-B345-066784478D24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {018F959E-824B-4664-B345-066784478D24}.Debug|x64.ActiveCfg = Debug|Any CPU + {018F959E-824B-4664-B345-066784478D24}.Debug|x64.Build.0 = Debug|Any CPU + {018F959E-824B-4664-B345-066784478D24}.Debug|x86.ActiveCfg = Debug|Any CPU + {018F959E-824B-4664-B345-066784478D24}.Debug|x86.Build.0 = Debug|Any CPU {018F959E-824B-4664-B345-066784478D24}.Release|Any CPU.ActiveCfg = Release|Any CPU {018F959E-824B-4664-B345-066784478D24}.Release|Any CPU.Build.0 = Release|Any CPU + {018F959E-824B-4664-B345-066784478D24}.Release|x64.ActiveCfg = Release|Any CPU + {018F959E-824B-4664-B345-066784478D24}.Release|x64.Build.0 = Release|Any CPU + {018F959E-824B-4664-B345-066784478D24}.Release|x86.ActiveCfg = Release|Any CPU + {018F959E-824B-4664-B345-066784478D24}.Release|x86.Build.0 = Release|Any CPU {7D36DDDA-9E0B-4D2C-8033-5D62FF8B6166}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7D36DDDA-9E0B-4D2C-8033-5D62FF8B6166}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D36DDDA-9E0B-4D2C-8033-5D62FF8B6166}.Debug|x64.ActiveCfg = Debug|Any CPU + {7D36DDDA-9E0B-4D2C-8033-5D62FF8B6166}.Debug|x64.Build.0 = Debug|Any CPU + {7D36DDDA-9E0B-4D2C-8033-5D62FF8B6166}.Debug|x86.ActiveCfg = Debug|Any CPU + {7D36DDDA-9E0B-4D2C-8033-5D62FF8B6166}.Debug|x86.Build.0 = Debug|Any CPU {7D36DDDA-9E0B-4D2C-8033-5D62FF8B6166}.Release|Any CPU.ActiveCfg = Release|Any CPU {7D36DDDA-9E0B-4D2C-8033-5D62FF8B6166}.Release|Any CPU.Build.0 = Release|Any CPU + {7D36DDDA-9E0B-4D2C-8033-5D62FF8B6166}.Release|x64.ActiveCfg = Release|Any CPU + {7D36DDDA-9E0B-4D2C-8033-5D62FF8B6166}.Release|x64.Build.0 = Release|Any CPU + {7D36DDDA-9E0B-4D2C-8033-5D62FF8B6166}.Release|x86.ActiveCfg = Release|Any CPU + {7D36DDDA-9E0B-4D2C-8033-5D62FF8B6166}.Release|x86.Build.0 = Release|Any CPU {4CCE599A-B9FE-4DF2-8763-34CF0A99D4AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4CCE599A-B9FE-4DF2-8763-34CF0A99D4AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4CCE599A-B9FE-4DF2-8763-34CF0A99D4AA}.Debug|x64.ActiveCfg = Debug|Any CPU + {4CCE599A-B9FE-4DF2-8763-34CF0A99D4AA}.Debug|x64.Build.0 = Debug|Any CPU + {4CCE599A-B9FE-4DF2-8763-34CF0A99D4AA}.Debug|x86.ActiveCfg = Debug|Any CPU + {4CCE599A-B9FE-4DF2-8763-34CF0A99D4AA}.Debug|x86.Build.0 = Debug|Any CPU {4CCE599A-B9FE-4DF2-8763-34CF0A99D4AA}.Release|Any CPU.ActiveCfg = Release|Any CPU {4CCE599A-B9FE-4DF2-8763-34CF0A99D4AA}.Release|Any CPU.Build.0 = Release|Any CPU + {4CCE599A-B9FE-4DF2-8763-34CF0A99D4AA}.Release|x64.ActiveCfg = Release|Any CPU + {4CCE599A-B9FE-4DF2-8763-34CF0A99D4AA}.Release|x64.Build.0 = Release|Any CPU + {4CCE599A-B9FE-4DF2-8763-34CF0A99D4AA}.Release|x86.ActiveCfg = Release|Any CPU + {4CCE599A-B9FE-4DF2-8763-34CF0A99D4AA}.Release|x86.Build.0 = Release|Any CPU {C559D52D-100B-4B2B-BE87-2344D835761D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C559D52D-100B-4B2B-BE87-2344D835761D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C559D52D-100B-4B2B-BE87-2344D835761D}.Debug|x64.ActiveCfg = Debug|Any CPU + {C559D52D-100B-4B2B-BE87-2344D835761D}.Debug|x64.Build.0 = Debug|Any CPU + {C559D52D-100B-4B2B-BE87-2344D835761D}.Debug|x86.ActiveCfg = Debug|Any CPU + {C559D52D-100B-4B2B-BE87-2344D835761D}.Debug|x86.Build.0 = Debug|Any CPU {C559D52D-100B-4B2B-BE87-2344D835761D}.Release|Any CPU.ActiveCfg = Release|Any CPU {C559D52D-100B-4B2B-BE87-2344D835761D}.Release|Any CPU.Build.0 = Release|Any CPU + {C559D52D-100B-4B2B-BE87-2344D835761D}.Release|x64.ActiveCfg = Release|Any CPU + {C559D52D-100B-4B2B-BE87-2344D835761D}.Release|x64.Build.0 = Release|Any CPU + {C559D52D-100B-4B2B-BE87-2344D835761D}.Release|x86.ActiveCfg = Release|Any CPU + {C559D52D-100B-4B2B-BE87-2344D835761D}.Release|x86.Build.0 = Release|Any CPU {09CE30F6-013A-49ED-B3D6-60AFA84682AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {09CE30F6-013A-49ED-B3D6-60AFA84682AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09CE30F6-013A-49ED-B3D6-60AFA84682AC}.Debug|x64.ActiveCfg = Debug|Any CPU + {09CE30F6-013A-49ED-B3D6-60AFA84682AC}.Debug|x64.Build.0 = Debug|Any CPU + {09CE30F6-013A-49ED-B3D6-60AFA84682AC}.Debug|x86.ActiveCfg = Debug|Any CPU + {09CE30F6-013A-49ED-B3D6-60AFA84682AC}.Debug|x86.Build.0 = Debug|Any CPU {09CE30F6-013A-49ED-B3D6-60AFA84682AC}.Release|Any CPU.ActiveCfg = Release|Any CPU {09CE30F6-013A-49ED-B3D6-60AFA84682AC}.Release|Any CPU.Build.0 = Release|Any CPU + {09CE30F6-013A-49ED-B3D6-60AFA84682AC}.Release|x64.ActiveCfg = Release|Any CPU + {09CE30F6-013A-49ED-B3D6-60AFA84682AC}.Release|x64.Build.0 = Release|Any CPU + {09CE30F6-013A-49ED-B3D6-60AFA84682AC}.Release|x86.ActiveCfg = Release|Any CPU + {09CE30F6-013A-49ED-B3D6-60AFA84682AC}.Release|x86.Build.0 = Release|Any CPU {CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Debug|x64.ActiveCfg = Debug|Any CPU + {CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Debug|x64.Build.0 = Debug|Any CPU + {CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Debug|x86.ActiveCfg = Debug|Any CPU + {CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Debug|x86.Build.0 = Debug|Any CPU {CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Release|Any CPU.ActiveCfg = Release|Any CPU {CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Release|Any CPU.Build.0 = Release|Any CPU + {CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Release|x64.ActiveCfg = Release|Any CPU + {CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Release|x64.Build.0 = Release|Any CPU + {CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Release|x86.ActiveCfg = Release|Any CPU + {CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Release|x86.Build.0 = Release|Any CPU {FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Debug|x64.ActiveCfg = Debug|Any CPU + {FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Debug|x64.Build.0 = Debug|Any CPU + {FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Debug|x86.ActiveCfg = Debug|Any CPU + {FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Debug|x86.Build.0 = Debug|Any CPU {FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Release|Any CPU.ActiveCfg = Release|Any CPU {FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Release|Any CPU.Build.0 = Release|Any CPU + {FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Release|x64.ActiveCfg = Release|Any CPU + {FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Release|x64.Build.0 = Release|Any CPU + {FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Release|x86.ActiveCfg = Release|Any CPU + {FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Release|x86.Build.0 = Release|Any CPU {C883AC18-7C6A-482E-A9D7-C44DF8633425}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C883AC18-7C6A-482E-A9D7-C44DF8633425}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C883AC18-7C6A-482E-A9D7-C44DF8633425}.Debug|x64.ActiveCfg = Debug|Any CPU + {C883AC18-7C6A-482E-A9D7-C44DF8633425}.Debug|x64.Build.0 = Debug|Any CPU + {C883AC18-7C6A-482E-A9D7-C44DF8633425}.Debug|x86.ActiveCfg = Debug|Any CPU + {C883AC18-7C6A-482E-A9D7-C44DF8633425}.Debug|x86.Build.0 = Debug|Any CPU {C883AC18-7C6A-482E-A9D7-C44DF8633425}.Release|Any CPU.ActiveCfg = Release|Any CPU {C883AC18-7C6A-482E-A9D7-C44DF8633425}.Release|Any CPU.Build.0 = Release|Any CPU + {C883AC18-7C6A-482E-A9D7-C44DF8633425}.Release|x64.ActiveCfg = Release|Any CPU + {C883AC18-7C6A-482E-A9D7-C44DF8633425}.Release|x64.Build.0 = Release|Any CPU + {C883AC18-7C6A-482E-A9D7-C44DF8633425}.Release|x86.ActiveCfg = Release|Any CPU + {C883AC18-7C6A-482E-A9D7-C44DF8633425}.Release|x86.Build.0 = Release|Any CPU {0331559E-4ED1-4A56-9C35-3EAD4D7E696D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0331559E-4ED1-4A56-9C35-3EAD4D7E696D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0331559E-4ED1-4A56-9C35-3EAD4D7E696D}.Debug|x64.ActiveCfg = Debug|Any CPU + {0331559E-4ED1-4A56-9C35-3EAD4D7E696D}.Debug|x64.Build.0 = Debug|Any CPU + {0331559E-4ED1-4A56-9C35-3EAD4D7E696D}.Debug|x86.ActiveCfg = Debug|Any CPU + {0331559E-4ED1-4A56-9C35-3EAD4D7E696D}.Debug|x86.Build.0 = Debug|Any CPU {0331559E-4ED1-4A56-9C35-3EAD4D7E696D}.Release|Any CPU.ActiveCfg = Release|Any CPU {0331559E-4ED1-4A56-9C35-3EAD4D7E696D}.Release|Any CPU.Build.0 = Release|Any CPU + {0331559E-4ED1-4A56-9C35-3EAD4D7E696D}.Release|x64.ActiveCfg = Release|Any CPU + {0331559E-4ED1-4A56-9C35-3EAD4D7E696D}.Release|x64.Build.0 = Release|Any CPU + {0331559E-4ED1-4A56-9C35-3EAD4D7E696D}.Release|x86.ActiveCfg = Release|Any CPU + {0331559E-4ED1-4A56-9C35-3EAD4D7E696D}.Release|x86.Build.0 = Release|Any CPU {89B83007-71E6-4B57-BA78-2544BFA476DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {89B83007-71E6-4B57-BA78-2544BFA476DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89B83007-71E6-4B57-BA78-2544BFA476DB}.Debug|x64.ActiveCfg = Debug|Any CPU + {89B83007-71E6-4B57-BA78-2544BFA476DB}.Debug|x64.Build.0 = Debug|Any CPU + {89B83007-71E6-4B57-BA78-2544BFA476DB}.Debug|x86.ActiveCfg = Debug|Any CPU + {89B83007-71E6-4B57-BA78-2544BFA476DB}.Debug|x86.Build.0 = Debug|Any CPU {89B83007-71E6-4B57-BA78-2544BFA476DB}.Release|Any CPU.ActiveCfg = Release|Any CPU {89B83007-71E6-4B57-BA78-2544BFA476DB}.Release|Any CPU.Build.0 = Release|Any CPU + {89B83007-71E6-4B57-BA78-2544BFA476DB}.Release|x64.ActiveCfg = Release|Any CPU + {89B83007-71E6-4B57-BA78-2544BFA476DB}.Release|x64.Build.0 = Release|Any CPU + {89B83007-71E6-4B57-BA78-2544BFA476DB}.Release|x86.ActiveCfg = Release|Any CPU + {89B83007-71E6-4B57-BA78-2544BFA476DB}.Release|x86.Build.0 = Release|Any CPU {111E7029-BB29-4039-9B45-04776798A8DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {111E7029-BB29-4039-9B45-04776798A8DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {111E7029-BB29-4039-9B45-04776798A8DD}.Debug|x64.ActiveCfg = Debug|Any CPU + {111E7029-BB29-4039-9B45-04776798A8DD}.Debug|x64.Build.0 = Debug|Any CPU + {111E7029-BB29-4039-9B45-04776798A8DD}.Debug|x86.ActiveCfg = Debug|Any CPU + {111E7029-BB29-4039-9B45-04776798A8DD}.Debug|x86.Build.0 = Debug|Any CPU {111E7029-BB29-4039-9B45-04776798A8DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {111E7029-BB29-4039-9B45-04776798A8DD}.Release|Any CPU.Build.0 = Release|Any CPU + {111E7029-BB29-4039-9B45-04776798A8DD}.Release|x64.ActiveCfg = Release|Any CPU + {111E7029-BB29-4039-9B45-04776798A8DD}.Release|x64.Build.0 = Release|Any CPU + {111E7029-BB29-4039-9B45-04776798A8DD}.Release|x86.ActiveCfg = Release|Any CPU + {111E7029-BB29-4039-9B45-04776798A8DD}.Release|x86.Build.0 = Release|Any CPU {164F55EC-9412-4CD4-81AD-3598B57632A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {164F55EC-9412-4CD4-81AD-3598B57632A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {164F55EC-9412-4CD4-81AD-3598B57632A6}.Debug|x64.ActiveCfg = Debug|Any CPU + {164F55EC-9412-4CD4-81AD-3598B57632A6}.Debug|x64.Build.0 = Debug|Any CPU + {164F55EC-9412-4CD4-81AD-3598B57632A6}.Debug|x86.ActiveCfg = Debug|Any CPU + {164F55EC-9412-4CD4-81AD-3598B57632A6}.Debug|x86.Build.0 = Debug|Any CPU {164F55EC-9412-4CD4-81AD-3598B57632A6}.Release|Any CPU.ActiveCfg = Release|Any CPU {164F55EC-9412-4CD4-81AD-3598B57632A6}.Release|Any CPU.Build.0 = Release|Any CPU + {164F55EC-9412-4CD4-81AD-3598B57632A6}.Release|x64.ActiveCfg = Release|Any CPU + {164F55EC-9412-4CD4-81AD-3598B57632A6}.Release|x64.Build.0 = Release|Any CPU + {164F55EC-9412-4CD4-81AD-3598B57632A6}.Release|x86.ActiveCfg = Release|Any CPU + {164F55EC-9412-4CD4-81AD-3598B57632A6}.Release|x86.Build.0 = Release|Any CPU {A272D3EC-FAAF-4795-A796-302725382AFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A272D3EC-FAAF-4795-A796-302725382AFF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A272D3EC-FAAF-4795-A796-302725382AFF}.Debug|x64.ActiveCfg = Debug|Any CPU + {A272D3EC-FAAF-4795-A796-302725382AFF}.Debug|x64.Build.0 = Debug|Any CPU + {A272D3EC-FAAF-4795-A796-302725382AFF}.Debug|x86.ActiveCfg = Debug|Any CPU + {A272D3EC-FAAF-4795-A796-302725382AFF}.Debug|x86.Build.0 = Debug|Any CPU {A272D3EC-FAAF-4795-A796-302725382AFF}.Release|Any CPU.ActiveCfg = Release|Any CPU {A272D3EC-FAAF-4795-A796-302725382AFF}.Release|Any CPU.Build.0 = Release|Any CPU + {A272D3EC-FAAF-4795-A796-302725382AFF}.Release|x64.ActiveCfg = Release|Any CPU + {A272D3EC-FAAF-4795-A796-302725382AFF}.Release|x64.Build.0 = Release|Any CPU + {A272D3EC-FAAF-4795-A796-302725382AFF}.Release|x86.ActiveCfg = Release|Any CPU + {A272D3EC-FAAF-4795-A796-302725382AFF}.Release|x86.Build.0 = Release|Any CPU {4DFECE72-4A1F-4B58-918E-DCD07B585231}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4DFECE72-4A1F-4B58-918E-DCD07B585231}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4DFECE72-4A1F-4B58-918E-DCD07B585231}.Debug|x64.ActiveCfg = Debug|Any CPU + {4DFECE72-4A1F-4B58-918E-DCD07B585231}.Debug|x64.Build.0 = Debug|Any CPU + {4DFECE72-4A1F-4B58-918E-DCD07B585231}.Debug|x86.ActiveCfg = Debug|Any CPU + {4DFECE72-4A1F-4B58-918E-DCD07B585231}.Debug|x86.Build.0 = Debug|Any CPU {4DFECE72-4A1F-4B58-918E-DCD07B585231}.Release|Any CPU.ActiveCfg = Release|Any CPU {4DFECE72-4A1F-4B58-918E-DCD07B585231}.Release|Any CPU.Build.0 = Release|Any CPU + {4DFECE72-4A1F-4B58-918E-DCD07B585231}.Release|x64.ActiveCfg = Release|Any CPU + {4DFECE72-4A1F-4B58-918E-DCD07B585231}.Release|x64.Build.0 = Release|Any CPU + {4DFECE72-4A1F-4B58-918E-DCD07B585231}.Release|x86.ActiveCfg = Release|Any CPU + {4DFECE72-4A1F-4B58-918E-DCD07B585231}.Release|x86.Build.0 = Release|Any CPU {2A83ED35-B631-4F02-8D4C-15611D0DB72C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2A83ED35-B631-4F02-8D4C-15611D0DB72C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A83ED35-B631-4F02-8D4C-15611D0DB72C}.Debug|x64.ActiveCfg = Debug|Any CPU + {2A83ED35-B631-4F02-8D4C-15611D0DB72C}.Debug|x64.Build.0 = Debug|Any CPU + {2A83ED35-B631-4F02-8D4C-15611D0DB72C}.Debug|x86.ActiveCfg = Debug|Any CPU + {2A83ED35-B631-4F02-8D4C-15611D0DB72C}.Debug|x86.Build.0 = Debug|Any CPU {2A83ED35-B631-4F02-8D4C-15611D0DB72C}.Release|Any CPU.ActiveCfg = Release|Any CPU {2A83ED35-B631-4F02-8D4C-15611D0DB72C}.Release|Any CPU.Build.0 = Release|Any CPU + {2A83ED35-B631-4F02-8D4C-15611D0DB72C}.Release|x64.ActiveCfg = Release|Any CPU + {2A83ED35-B631-4F02-8D4C-15611D0DB72C}.Release|x64.Build.0 = Release|Any CPU + {2A83ED35-B631-4F02-8D4C-15611D0DB72C}.Release|x86.ActiveCfg = Release|Any CPU + {2A83ED35-B631-4F02-8D4C-15611D0DB72C}.Release|x86.Build.0 = Release|Any CPU {F30B90AD-1A01-4A6F-9699-809FA6875B22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F30B90AD-1A01-4A6F-9699-809FA6875B22}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F30B90AD-1A01-4A6F-9699-809FA6875B22}.Debug|x64.ActiveCfg = Debug|Any CPU + {F30B90AD-1A01-4A6F-9699-809FA6875B22}.Debug|x64.Build.0 = Debug|Any CPU + {F30B90AD-1A01-4A6F-9699-809FA6875B22}.Debug|x86.ActiveCfg = Debug|Any CPU + {F30B90AD-1A01-4A6F-9699-809FA6875B22}.Debug|x86.Build.0 = Debug|Any CPU {F30B90AD-1A01-4A6F-9699-809FA6875B22}.Release|Any CPU.ActiveCfg = Release|Any CPU {F30B90AD-1A01-4A6F-9699-809FA6875B22}.Release|Any CPU.Build.0 = Release|Any CPU + {F30B90AD-1A01-4A6F-9699-809FA6875B22}.Release|x64.ActiveCfg = Release|Any CPU + {F30B90AD-1A01-4A6F-9699-809FA6875B22}.Release|x64.Build.0 = Release|Any CPU + {F30B90AD-1A01-4A6F-9699-809FA6875B22}.Release|x86.ActiveCfg = Release|Any CPU + {F30B90AD-1A01-4A6F-9699-809FA6875B22}.Release|x86.Build.0 = Release|Any CPU {AE3FC78E-167F-4B6E-88EC-84743EB748B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AE3FC78E-167F-4B6E-88EC-84743EB748B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE3FC78E-167F-4B6E-88EC-84743EB748B7}.Debug|x64.ActiveCfg = Debug|Any CPU + {AE3FC78E-167F-4B6E-88EC-84743EB748B7}.Debug|x64.Build.0 = Debug|Any CPU + {AE3FC78E-167F-4B6E-88EC-84743EB748B7}.Debug|x86.ActiveCfg = Debug|Any CPU + {AE3FC78E-167F-4B6E-88EC-84743EB748B7}.Debug|x86.Build.0 = Debug|Any CPU {AE3FC78E-167F-4B6E-88EC-84743EB748B7}.Release|Any CPU.ActiveCfg = Release|Any CPU {AE3FC78E-167F-4B6E-88EC-84743EB748B7}.Release|Any CPU.Build.0 = Release|Any CPU + {AE3FC78E-167F-4B6E-88EC-84743EB748B7}.Release|x64.ActiveCfg = Release|Any CPU + {AE3FC78E-167F-4B6E-88EC-84743EB748B7}.Release|x64.Build.0 = Release|Any CPU + {AE3FC78E-167F-4B6E-88EC-84743EB748B7}.Release|x86.ActiveCfg = Release|Any CPU + {AE3FC78E-167F-4B6E-88EC-84743EB748B7}.Release|x86.Build.0 = Release|Any CPU {C6A121C5-DEB1-4FCE-9140-AF144EA98EEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C6A121C5-DEB1-4FCE-9140-AF144EA98EEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6A121C5-DEB1-4FCE-9140-AF144EA98EEE}.Debug|x64.ActiveCfg = Debug|Any CPU + {C6A121C5-DEB1-4FCE-9140-AF144EA98EEE}.Debug|x64.Build.0 = Debug|Any CPU + {C6A121C5-DEB1-4FCE-9140-AF144EA98EEE}.Debug|x86.ActiveCfg = Debug|Any CPU + {C6A121C5-DEB1-4FCE-9140-AF144EA98EEE}.Debug|x86.Build.0 = Debug|Any CPU {C6A121C5-DEB1-4FCE-9140-AF144EA98EEE}.Release|Any CPU.ActiveCfg = Release|Any CPU {C6A121C5-DEB1-4FCE-9140-AF144EA98EEE}.Release|Any CPU.Build.0 = Release|Any CPU + {C6A121C5-DEB1-4FCE-9140-AF144EA98EEE}.Release|x64.ActiveCfg = Release|Any CPU + {C6A121C5-DEB1-4FCE-9140-AF144EA98EEE}.Release|x64.Build.0 = Release|Any CPU + {C6A121C5-DEB1-4FCE-9140-AF144EA98EEE}.Release|x86.ActiveCfg = Release|Any CPU + {C6A121C5-DEB1-4FCE-9140-AF144EA98EEE}.Release|x86.Build.0 = Release|Any CPU {094433A4-504F-4E12-959F-CCB1965C1C9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {094433A4-504F-4E12-959F-CCB1965C1C9A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {094433A4-504F-4E12-959F-CCB1965C1C9A}.Debug|x64.ActiveCfg = Debug|Any CPU + {094433A4-504F-4E12-959F-CCB1965C1C9A}.Debug|x64.Build.0 = Debug|Any CPU + {094433A4-504F-4E12-959F-CCB1965C1C9A}.Debug|x86.ActiveCfg = Debug|Any CPU + {094433A4-504F-4E12-959F-CCB1965C1C9A}.Debug|x86.Build.0 = Debug|Any CPU {094433A4-504F-4E12-959F-CCB1965C1C9A}.Release|Any CPU.ActiveCfg = Release|Any CPU {094433A4-504F-4E12-959F-CCB1965C1C9A}.Release|Any CPU.Build.0 = Release|Any CPU + {094433A4-504F-4E12-959F-CCB1965C1C9A}.Release|x64.ActiveCfg = Release|Any CPU + {094433A4-504F-4E12-959F-CCB1965C1C9A}.Release|x64.Build.0 = Release|Any CPU + {094433A4-504F-4E12-959F-CCB1965C1C9A}.Release|x86.ActiveCfg = Release|Any CPU + {094433A4-504F-4E12-959F-CCB1965C1C9A}.Release|x86.Build.0 = Release|Any CPU {E6EA955D-D0A7-4749-9586-0F7256EF5C5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E6EA955D-D0A7-4749-9586-0F7256EF5C5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E6EA955D-D0A7-4749-9586-0F7256EF5C5E}.Debug|x64.ActiveCfg = Debug|Any CPU + {E6EA955D-D0A7-4749-9586-0F7256EF5C5E}.Debug|x64.Build.0 = Debug|Any CPU + {E6EA955D-D0A7-4749-9586-0F7256EF5C5E}.Debug|x86.ActiveCfg = Debug|Any CPU + {E6EA955D-D0A7-4749-9586-0F7256EF5C5E}.Debug|x86.Build.0 = Debug|Any CPU {E6EA955D-D0A7-4749-9586-0F7256EF5C5E}.Release|Any CPU.ActiveCfg = Release|Any CPU {E6EA955D-D0A7-4749-9586-0F7256EF5C5E}.Release|Any CPU.Build.0 = Release|Any CPU + {E6EA955D-D0A7-4749-9586-0F7256EF5C5E}.Release|x64.ActiveCfg = Release|Any CPU + {E6EA955D-D0A7-4749-9586-0F7256EF5C5E}.Release|x64.Build.0 = Release|Any CPU + {E6EA955D-D0A7-4749-9586-0F7256EF5C5E}.Release|x86.ActiveCfg = Release|Any CPU + {E6EA955D-D0A7-4749-9586-0F7256EF5C5E}.Release|x86.Build.0 = Release|Any CPU {153FC4AD-F5B0-4100-990E-0987C86DBF01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {153FC4AD-F5B0-4100-990E-0987C86DBF01}.Debug|Any CPU.Build.0 = Debug|Any CPU + {153FC4AD-F5B0-4100-990E-0987C86DBF01}.Debug|x64.ActiveCfg = Debug|Any CPU + {153FC4AD-F5B0-4100-990E-0987C86DBF01}.Debug|x64.Build.0 = Debug|Any CPU + {153FC4AD-F5B0-4100-990E-0987C86DBF01}.Debug|x86.ActiveCfg = Debug|Any CPU + {153FC4AD-F5B0-4100-990E-0987C86DBF01}.Debug|x86.Build.0 = Debug|Any CPU {153FC4AD-F5B0-4100-990E-0987C86DBF01}.Release|Any CPU.ActiveCfg = Release|Any CPU {153FC4AD-F5B0-4100-990E-0987C86DBF01}.Release|Any CPU.Build.0 = Release|Any CPU + {153FC4AD-F5B0-4100-990E-0987C86DBF01}.Release|x64.ActiveCfg = Release|Any CPU + {153FC4AD-F5B0-4100-990E-0987C86DBF01}.Release|x64.Build.0 = Release|Any CPU + {153FC4AD-F5B0-4100-990E-0987C86DBF01}.Release|x86.ActiveCfg = Release|Any CPU + {153FC4AD-F5B0-4100-990E-0987C86DBF01}.Release|x86.Build.0 = Release|Any CPU {AABD3EF7-8C86-4981-B1D2-B1F786F33069}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AABD3EF7-8C86-4981-B1D2-B1F786F33069}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AABD3EF7-8C86-4981-B1D2-B1F786F33069}.Debug|x64.ActiveCfg = Debug|Any CPU + {AABD3EF7-8C86-4981-B1D2-B1F786F33069}.Debug|x64.Build.0 = Debug|Any CPU + {AABD3EF7-8C86-4981-B1D2-B1F786F33069}.Debug|x86.ActiveCfg = Debug|Any CPU + {AABD3EF7-8C86-4981-B1D2-B1F786F33069}.Debug|x86.Build.0 = Debug|Any CPU {AABD3EF7-8C86-4981-B1D2-B1F786F33069}.Release|Any CPU.ActiveCfg = Release|Any CPU {AABD3EF7-8C86-4981-B1D2-B1F786F33069}.Release|Any CPU.Build.0 = Release|Any CPU + {AABD3EF7-8C86-4981-B1D2-B1F786F33069}.Release|x64.ActiveCfg = Release|Any CPU + {AABD3EF7-8C86-4981-B1D2-B1F786F33069}.Release|x64.Build.0 = Release|Any CPU + {AABD3EF7-8C86-4981-B1D2-B1F786F33069}.Release|x86.ActiveCfg = Release|Any CPU + {AABD3EF7-8C86-4981-B1D2-B1F786F33069}.Release|x86.Build.0 = Release|Any CPU + {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Debug|x64.ActiveCfg = Debug|Any CPU + {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Debug|x64.Build.0 = Debug|Any CPU + {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Debug|x86.ActiveCfg = Debug|Any CPU + {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Debug|x86.Build.0 = Debug|Any CPU + {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Release|Any CPU.Build.0 = Release|Any CPU + {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Release|x64.ActiveCfg = Release|Any CPU + {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Release|x64.Build.0 = Release|Any CPU + {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Release|x86.ActiveCfg = Release|Any CPU + {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {4D198E25-C211-41DC-9E84-B15E89BD7048} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A} + {01F05AD0-E0E0-401F-A7EC-905928E1E9F0} = {73ABAE37-118F-4A53-BC2C-F19333555C90} {B27C5107-128B-465A-B8F8-8985399E4CFB} = {67B576EE-02FA-4F9B-94BC-3630BC09ECE5} {CD2887E3-BDA9-434B-A5BF-9ED38DE20332} = {245023D2-D3CA-47B9-831D-DAB91A2FFDC7} {A2A34BBC-CB5E-4100-9529-A12B6ECB769C} = {245023D2-D3CA-47B9-831D-DAB91A2FFDC7} {018F959E-824B-4664-B345-066784478D24} = {67B576EE-02FA-4F9B-94BC-3630BC09ECE5} + {7D36DDDA-9E0B-4D2C-8033-5D62FF8B6166} = {059E787F-85C1-43BE-9DD6-CE319E106383} + {4CCE599A-B9FE-4DF2-8763-34CF0A99D4AA} = {73ABAE37-118F-4A53-BC2C-F19333555C90} {6E2ED6CC-AFC1-4E58-965D-6AEC500EBB46} = {245023D2-D3CA-47B9-831D-DAB91A2FFDC7} {6554F917-73CE-4B3D-9101-F28EAA762C6B} = {245023D2-D3CA-47B9-831D-DAB91A2FFDC7} - {4894063D-0DEF-4B7E-97D0-0D0A5B85C608} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A} {C559D52D-100B-4B2B-BE87-2344D835761D} = {4894063D-0DEF-4B7E-97D0-0D0A5B85C608} + {4894063D-0DEF-4B7E-97D0-0D0A5B85C608} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A} {BB789671-B262-43DD-91DB-39F9186B8257} = {245023D2-D3CA-47B9-831D-DAB91A2FFDC7} {09CE30F6-013A-49ED-B3D6-60AFA84682AC} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A} {CD94F9E4-7FCD-4152-81F1-4288C6B75367} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A} {73ABAE37-118F-4A53-BC2C-F19333555C90} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A} - {01F05AD0-E0E0-401F-A7EC-905928E1E9F0} = {73ABAE37-118F-4A53-BC2C-F19333555C90} - {4CCE599A-B9FE-4DF2-8763-34CF0A99D4AA} = {73ABAE37-118F-4A53-BC2C-F19333555C90} {059E787F-85C1-43BE-9DD6-CE319E106383} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A} - {7D36DDDA-9E0B-4D2C-8033-5D62FF8B6166} = {059E787F-85C1-43BE-9DD6-CE319E106383} {FB1C1954-D8E2-4745-BA62-04DD82FB4792} = {245023D2-D3CA-47B9-831D-DAB91A2FFDC7} {FD1AC230-798B-4AB9-8CE6-A06264885DBC} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A} {C883AC18-7C6A-482E-A9D7-C44DF8633425} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A} @@ -300,5 +545,6 @@ Global {E6EA955D-D0A7-4749-9586-0F7256EF5C5E} = {7AACA67B-3C56-4C7C-9891-558589FC52DB} {153FC4AD-F5B0-4100-990E-0987C86DBF01} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A} {AABD3EF7-8C86-4981-B1D2-B1F786F33069} = {7AACA67B-3C56-4C7C-9891-558589FC52DB} + {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4} = {67B576EE-02FA-4F9B-94BC-3630BC09ECE5} EndGlobalSection EndGlobal diff --git a/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/AgentBuilderStreamTransformer.cs b/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/AgentBuilderStreamTransformer.cs index 1b5e3d1a3..44b1c5c00 100644 --- a/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/AgentBuilderStreamTransformer.cs +++ b/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/AgentBuilderStreamTransformer.cs @@ -2,9 +2,6 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information -using System.Buffers; -using System.IO.Pipelines; -using System.Text; using System.Text.Json; using Elastic.Documentation.Api.Core.AskAi; using Microsoft.Extensions.Logging; diff --git a/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/LlmGatewayAskAiGateway.cs b/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/LlmGatewayAskAiGateway.cs index 8626af230..f7d1cdf70 100644 --- a/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/LlmGatewayAskAiGateway.cs +++ b/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/LlmGatewayAskAiGateway.cs @@ -68,7 +68,7 @@ public static LlmGatewayRequest CreateFromRequest(AskAiRequest request) => new ChatInput("user", AskAiRequest.SystemPrompt), new ChatInput("user", request.Message) ], - ThreadId: request.ConversationId ?? "elastic-docs-" + Guid.NewGuid() + ThreadId: request.ConversationId ?? Guid.NewGuid().ToString() ); } diff --git a/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/LlmGatewayStreamTransformer.cs b/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/LlmGatewayStreamTransformer.cs index 2e150bba5..b0c21ec36 100644 --- a/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/LlmGatewayStreamTransformer.cs +++ b/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/LlmGatewayStreamTransformer.cs @@ -2,6 +2,8 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.Diagnostics; +using System.IO.Pipelines; using System.Text.Json; using Elastic.Documentation.Api.Core.AskAi; using Microsoft.Extensions.Logging; @@ -15,6 +17,36 @@ public class LlmGatewayStreamTransformer(ILogger lo { protected override string GetAgentId() => LlmGatewayAskAiGateway.ModelName; protected override string GetAgentProvider() => LlmGatewayAskAiGateway.ProviderName; + + /// + /// Override to emit ConversationStart event when conversationId is null (new conversation) + /// + protected override async Task ProcessStreamAsync(PipeReader reader, PipeWriter writer, string? conversationId, Activity? parentActivity, CancellationToken cancellationToken) + { + // If conversationId is null, generate a new one and emit ConversationStart event + // This matches the ThreadId format used in LlmGatewayAskAiGateway + var actualConversationId = conversationId; + if (conversationId == null) + { + actualConversationId = Guid.NewGuid().ToString(); + var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + var conversationStartEvent = new AskAiEvent.ConversationStart( + Id: Guid.NewGuid().ToString(), + Timestamp: timestamp, + ConversationId: actualConversationId + ); + + // Set activity tags for the new conversation + _ = parentActivity?.SetTag("gen_ai.conversation.id", actualConversationId); + Logger.LogDebug("LLM Gateway conversation started: {ConversationId}", actualConversationId); + + // Write the ConversationStart event to the stream + await WriteEventAsync(conversationStartEvent, writer, cancellationToken); + } + + // Continue with normal stream processing using the actual conversation ID + await base.ProcessStreamAsync(reader, writer, actualConversationId, parentActivity, cancellationToken); + } protected override AskAiEvent? TransformJsonEvent(string? conversationId, string? eventType, JsonElement json) { // LLM Gateway format: ["custom", {type: "...", ...}] diff --git a/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/SseParser.cs b/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/SseParser.cs new file mode 100644 index 000000000..573ce990a --- /dev/null +++ b/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/SseParser.cs @@ -0,0 +1,101 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Buffers; +using System.IO.Pipelines; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Elastic.Documentation.Api.Infrastructure.Adapters.AskAi; + +/// +/// Represents a parsed Server-Sent Event (SSE) +/// +/// The event type from the "event:" field, or null if not specified +/// The accumulated data from all "data:" fields +public record SseEvent(string? EventType, string Data); + +/// +/// Parser for Server-Sent Events (SSE) following the W3C SSE specification. +/// +public static class SseParser +{ + /// + /// Parse Server-Sent Events (SSE) from a PipeReader following the W3C SSE specification. + /// This method handles the standard SSE format with event:, data:, and comment lines. + /// + public static async IAsyncEnumerable ParseAsync( + PipeReader reader, + [EnumeratorCancellation] CancellationToken cancellationToken = default + ) + { + string? currentEvent = null; + var dataBuilder = new StringBuilder(); + + while (!cancellationToken.IsCancellationRequested) + { + var result = await reader.ReadAsync(cancellationToken); + var buffer = result.Buffer; + + // Process all complete lines in the buffer + while (TryReadLine(ref buffer, out var line)) + { + // SSE comment line - skip + if (line.Length > 0 && line[0] == ':') + continue; + + // Event type line + if (line.StartsWith("event:", StringComparison.Ordinal)) + currentEvent = line[6..].Trim(); + // Data line + else if (line.StartsWith("data:", StringComparison.Ordinal)) + _ = dataBuilder.Append(line[5..].Trim()); + // Empty line - marks end of event + else if (string.IsNullOrEmpty(line)) + { + if (dataBuilder.Length <= 0) + continue; + yield return new SseEvent(currentEvent, dataBuilder.ToString()); + currentEvent = null; + _ = dataBuilder.Clear(); + } + } + + // Tell the PipeReader how much of the buffer we consumed + reader.AdvanceTo(buffer.Start, buffer.End); + + // Stop reading if there's no more data coming + if (!result.IsCompleted) + continue; + + // Yield any remaining event that hasn't been terminated with an empty line + if (dataBuilder.Length > 0) + yield return new SseEvent(currentEvent, dataBuilder.ToString()); + break; + } + } + + /// + /// Try to read a single line from the buffer + /// + private static bool TryReadLine(ref ReadOnlySequence buffer, out string line) + { + // Look for a line ending + var position = buffer.PositionOf((byte)'\n'); + + if (position == null) + { + line = string.Empty; + return false; + } + + // Extract the line (excluding the \n) + var lineSlice = buffer.Slice(0, position.Value); + line = Encoding.UTF8.GetString(lineSlice).TrimEnd('\r'); + + // Skip past the line + \n + buffer = buffer.Slice(buffer.GetPosition(1, position.Value)); + return true; + } +} diff --git a/src/api/Elastic.Documentation.Api.Core/AskAi/StreamTransformerBase.cs b/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/StreamTransformerBase.cs similarity index 76% rename from src/api/Elastic.Documentation.Api.Core/AskAi/StreamTransformerBase.cs rename to src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/StreamTransformerBase.cs index 1a2ca22b7..f0193019a 100644 --- a/src/api/Elastic.Documentation.Api.Core/AskAi/StreamTransformerBase.cs +++ b/src/api/Elastic.Documentation.Api.Infrastructure/Adapters/AskAi/StreamTransformerBase.cs @@ -2,22 +2,15 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information -using System.Buffers; using System.Diagnostics; using System.IO.Pipelines; -using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; +using Elastic.Documentation.Api.Core; +using Elastic.Documentation.Api.Core.AskAi; using Microsoft.Extensions.Logging; -namespace Elastic.Documentation.Api.Core.AskAi; - -/// -/// Represents a parsed Server-Sent Event (SSE) -/// -/// The event type from the "event:" field, or null if not specified -/// The accumulated data from all "data:" fields -public record SseEvent(string? EventType, string Data); +namespace Elastic.Documentation.Api.Infrastructure.Adapters.AskAi; /// /// Base class for stream transformers that handles common streaming logic @@ -129,7 +122,7 @@ private async Task ProcessPipeAsync(PipeReader reader, PipeWriter writer, string /// Default implementation parses SSE events and JSON, then calls TransformJsonEvent. /// /// Stream processing result with metrics and captured output - private async Task ProcessStreamAsync(PipeReader reader, PipeWriter writer, string? conversationId, Activity? parentActivity, CancellationToken cancellationToken) + protected virtual async Task ProcessStreamAsync(PipeReader reader, PipeWriter writer, string? conversationId, Activity? parentActivity, CancellationToken cancellationToken) { using var activity = StreamTransformerActivitySource.StartActivity(nameof(ProcessStreamAsync)); @@ -137,7 +130,7 @@ private async Task ProcessStreamAsync(PipeReader reader, PipeWriter writer, stri _ = activity?.SetParentId(parentActivity.Id); List outputMessageParts = []; - await foreach (var sseEvent in ParseSseEventsAsync(reader, cancellationToken)) + await foreach (var sseEvent in SseParser.ParseAsync(reader, cancellationToken)) { AskAiEvent? transformedEvent; try @@ -255,7 +248,7 @@ private async Task ProcessStreamAsync(PipeReader reader, PipeWriter writer, stri /// /// Write a transformed event to the output stream /// - private async Task WriteEventAsync(AskAiEvent? transformedEvent, PipeWriter writer, CancellationToken cancellationToken) + protected async Task WriteEventAsync(AskAiEvent? transformedEvent, PipeWriter writer, CancellationToken cancellationToken) { if (transformedEvent == null) return; @@ -277,82 +270,4 @@ private async Task WriteEventAsync(AskAiEvent? transformedEvent, PipeWriter writ throw; // Re-throw to be handled by caller } } - - /// - /// Parse Server-Sent Events (SSE) from a PipeReader following the W3C SSE specification. - /// This method handles the standard SSE format with event:, data:, and comment lines. - /// - private static async IAsyncEnumerable ParseSseEventsAsync( - PipeReader reader, - [EnumeratorCancellation] Cancel cancellationToken - ) - { - string? currentEvent = null; - var dataBuilder = new StringBuilder(); - - while (!cancellationToken.IsCancellationRequested) - { - var result = await reader.ReadAsync(cancellationToken); - var buffer = result.Buffer; - - // Process all complete lines in the buffer - while (TryReadLine(ref buffer, out var line)) - { - // SSE comment line - skip - if (line.Length > 0 && line[0] == ':') - continue; - - // Event type line - if (line.StartsWith("event:", StringComparison.Ordinal)) - currentEvent = line[6..].Trim(); - // Data line - else if (line.StartsWith("data:", StringComparison.Ordinal)) - _ = dataBuilder.Append(line[5..].Trim()); - // Empty line - marks end of event - else if (string.IsNullOrEmpty(line)) - { - if (dataBuilder.Length <= 0) - continue; - yield return new SseEvent(currentEvent, dataBuilder.ToString()); - currentEvent = null; - _ = dataBuilder.Clear(); - } - } - - // Tell the PipeReader how much of the buffer we consumed - reader.AdvanceTo(buffer.Start, buffer.End); - - // Stop reading if there's no more data coming - if (!result.IsCompleted) - continue; - - // Yield any remaining event that hasn't been terminated with an empty line - if (dataBuilder.Length > 0) - yield return new SseEvent(currentEvent, dataBuilder.ToString()); - break; - } - } - - /// - /// Try to read a single line from the buffer - /// - private static bool TryReadLine(ref ReadOnlySequence buffer, out string line) - { - // Look for a line ending - var position = buffer.PositionOf((byte)'\n'); - - if (position == null) - { - line = string.Empty; - return false; - } - - // Extract the line (excluding the \n) - var lineSlice = buffer.Slice(0, position.Value); - line = Encoding.UTF8.GetString(lineSlice).TrimEnd('\r'); - - // Skip past the line + \n - buffer = buffer.Slice(buffer.GetPosition(1, position.Value)); - return true; - } } diff --git a/tests/Elastic.Documentation.Api.Infrastructure.Tests/Adapters/AskAi/StreamTransformerTests.cs b/tests/Elastic.Documentation.Api.Infrastructure.Tests/Adapters/AskAi/StreamTransformerTests.cs index df3187920..384ffaaa2 100644 --- a/tests/Elastic.Documentation.Api.Infrastructure.Tests/Adapters/AskAi/StreamTransformerTests.cs +++ b/tests/Elastic.Documentation.Api.Infrastructure.Tests/Adapters/AskAi/StreamTransformerTests.cs @@ -2,16 +2,39 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.IO.Pipelines; using System.Text; using System.Text.Json; using Elastic.Documentation.Api.Core.AskAi; using Elastic.Documentation.Api.Infrastructure.Adapters.AskAi; using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; -using Xunit; namespace Elastic.Documentation.Api.Infrastructure.Tests.Adapters.AskAi; +/// +/// Test helper to parse AskAiEvents from transformer output streams. +/// Uses the production SseParser to read SSE format. +/// +internal static class StreamTransformerTestHelpers +{ + /// + /// Parse AskAiEvents from a transformer output stream for test assertions. + /// + public static async Task> ParseAskAiEventsAsync(Stream stream) + { + var events = new List(); + var reader = PipeReader.Create(stream); + await foreach (var sseEvent in SseParser.ParseAsync(reader, CancellationToken.None)) + { + var evt = JsonSerializer.Deserialize(sseEvent.Data, AskAiEventJsonContext.Default.AskAiEvent); + if (evt != null) + events.Add(evt); + } + return events; + } +} + public class AgentBuilderStreamTransformerTests { private readonly AgentBuilderStreamTransformer _transformer; @@ -54,8 +77,8 @@ public async Task TransformAsyncWithRealAgentBuilderPayloadParsesAllEventTypes() var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(sseData)); // Act - var outputStream = await _transformer.TransformAsync(inputStream, CancellationToken.None); - var events = await ParseEventsFromStream(outputStream); + var outputStream = await _transformer.TransformAsync(inputStream, null, null, CancellationToken.None); + var events = await StreamTransformerTestHelpers.ParseAskAiEventsAsync(outputStream); // Assert // Note: Due to async streaming, the final event might not be written before the input stream closes @@ -67,8 +90,8 @@ public async Task TransformAsyncWithRealAgentBuilderPayloadParsesAllEventTypes() events.Should().ContainSingle(e => e is AskAiEvent.Reasoning); events.Should().ContainSingle(e => e is AskAiEvent.SearchToolCall); events.Should().ContainSingle(e => e is AskAiEvent.ToolResult); - events.Should().Contain(e => e is AskAiEvent.Chunk); - events.Should().ContainSingle(e => e is AskAiEvent.ChunkComplete); + events.Should().Contain(e => e is AskAiEvent.MessageChunk); + events.Should().ContainSingle(e => e is AskAiEvent.MessageComplete); // Verify specific content var convStart = events.OfType().First(); @@ -87,12 +110,12 @@ public async Task TransformAsyncWithRealAgentBuilderPayloadParsesAllEventTypes() toolResult.ToolCallId.Should().Be("tooluse_abc123"); toolResult.Result.Should().Contain("semantic-docs-prod-latest"); - var chunks = events.OfType().ToList(); + var chunks = events.OfType().ToList(); chunks.Should().HaveCount(2); chunks[0].Content.Should().Be("Hello"); chunks[1].Content.Should().Be(" world"); - var complete = events.OfType().First(); + var complete = events.OfType().First(); complete.FullContent.Should().Be("Hello world"); } @@ -116,12 +139,12 @@ public async Task TransformAsyncWithKeepAliveCommentsSkipsThem() var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(sseData)); // Act - var outputStream = await _transformer.TransformAsync(inputStream, CancellationToken.None); - var events = await ParseEventsFromStream(outputStream); + var outputStream = await _transformer.TransformAsync(inputStream, null, null, CancellationToken.None); + var events = await StreamTransformerTestHelpers.ParseAskAiEventsAsync(outputStream); // Assert - Should have at least 1 event (round_complete might not be written in time) events.Should().HaveCountGreaterOrEqualTo(1); - events[0].Should().BeOfType(); + events[0].Should().BeOfType(); } [Fact] @@ -139,8 +162,8 @@ public async Task TransformAsyncWithMultilineDataFieldsAccumulatesCorrectly() var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(sseData)); // Act - var outputStream = await _transformer.TransformAsync(inputStream, CancellationToken.None); - var events = await ParseEventsFromStream(outputStream); + var outputStream = await _transformer.TransformAsync(inputStream, null, null, CancellationToken.None); + var events = await StreamTransformerTestHelpers.ParseAskAiEventsAsync(outputStream); // Assert - This test has malformed SSE data (missing proper blank line terminator) @@ -149,34 +172,6 @@ public async Task TransformAsyncWithMultilineDataFieldsAccumulatesCorrectly() events.Should().HaveCountGreaterOrEqualTo(0); } - private static async Task> ParseEventsFromStream(Stream stream) - { - var events = new List(); - - // Copy to memory stream to ensure all data is available - var ms = new MemoryStream(); - await stream.CopyToAsync(ms); - ms.Position = 0; - - using var reader = new StreamReader(ms, Encoding.UTF8); - - while (!reader.EndOfStream) - { - var line = await reader.ReadLineAsync(); - if (line == null) - break; - - if (line.StartsWith("data: ", StringComparison.Ordinal)) - { - var json = line.Substring(6); - var evt = JsonSerializer.Deserialize(json, AskAiEventJsonContext.Default.AskAiEvent); - if (evt != null) - events.Add(evt); - } - } - - return events; - } } public class LlmGatewayStreamTransformerTests @@ -216,8 +211,8 @@ public async Task TransformAsyncWithRealLlmGatewayPayloadParsesAllEventTypes() var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(sseData)); // Act - var outputStream = await _transformer.TransformAsync(inputStream, CancellationToken.None); - var events = await ParseEventsFromStream(outputStream); + var outputStream = await _transformer.TransformAsync(inputStream, null, null, CancellationToken.None); + var events = await StreamTransformerTestHelpers.ParseAskAiEventsAsync(outputStream); // Assert events.Should().HaveCount(7); @@ -226,16 +221,20 @@ public async Task TransformAsyncWithRealLlmGatewayPayloadParsesAllEventTypes() events[0].Should().BeOfType(); var convStart = events[0] as AskAiEvent.ConversationStart; convStart!.ConversationId.Should().NotBeNullOrEmpty(); - Guid.TryParse(convStart.ConversationId, out _).Should().BeTrue(); + + // convStart!.ConversationId.Should().Be("1"); + + _ = Guid.TryParse(convStart.ConversationId, out _).Should().BeTrue(); + // Event 2: ai_message_chunk (first) - events[1].Should().BeOfType(); - var chunk1 = events[1] as AskAiEvent.Chunk; + events[1].Should().BeOfType(); + var chunk1 = events[1] as AskAiEvent.MessageChunk; chunk1!.Content.Should().Be("Hello"); // Event 3: ai_message_chunk (second) - events[2].Should().BeOfType(); - var chunk2 = events[2] as AskAiEvent.Chunk; + events[2].Should().BeOfType(); + var chunk2 = events[2] as AskAiEvent.MessageChunk; chunk2!.Content.Should().Be(" world"); // Event 4: tool_call -> Should be SearchToolCall with extracted query @@ -251,8 +250,8 @@ public async Task TransformAsyncWithRealLlmGatewayPayloadParsesAllEventTypes() toolResult.Result.Should().Contain("Found 10 docs"); // Event 6: ai_message - events[5].Should().BeOfType(); - var complete = events[5] as AskAiEvent.ChunkComplete; + events[5].Should().BeOfType(); + var complete = events[5] as AskAiEvent.MessageComplete; complete!.FullContent.Should().Be("Hello world"); // Event 7: agent_end @@ -265,13 +264,13 @@ public async Task TransformAsyncWithEmptyDataLinesSkipsThem() // Arrange var sseData = """ event: agent_stream_output - data: + data: event: agent_stream_output data: [null, {"type":"agent_start","id":"1","timestamp":1234567890,"data":{}}] event: agent_stream_output - data: + data: event: agent_stream_output data: [null, {"type":"agent_end","id":"2","timestamp":1234567891,"data":{}}] @@ -281,8 +280,8 @@ public async Task TransformAsyncWithEmptyDataLinesSkipsThem() var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(sseData)); // Act - var outputStream = await _transformer.TransformAsync(inputStream, CancellationToken.None); - var events = await ParseEventsFromStream(outputStream); + var outputStream = await _transformer.TransformAsync(inputStream, null, null, CancellationToken.None); + var events = await StreamTransformerTestHelpers.ParseAskAiEventsAsync(outputStream); // Assert - Should only have 2 events events.Should().HaveCount(2); @@ -306,40 +305,143 @@ public async Task TransformAsyncSkipsModelLifecycleEvents() var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(sseData)); // Act - var outputStream = await _transformer.TransformAsync(inputStream, CancellationToken.None); - var events = await ParseEventsFromStream(outputStream); + var outputStream = await _transformer.TransformAsync(inputStream, null, null, CancellationToken.None); + var events = await StreamTransformerTestHelpers.ParseAskAiEventsAsync(outputStream); // Assert - Should only have the message chunk, model events skipped - events.Should().HaveCount(1); - events[0].Should().BeOfType(); + events.Should().HaveCount(2); + events[0].Should().BeOfType(); + events[1].Should().BeOfType(); + } + +} + +/// +/// Parameterized tests for common behaviors expected from all stream transformers. +/// These tests ensure consistency across different transformer implementations. +/// Adding a new transformer? Just add it to StreamTransformerTestCases() and these tests will automatically run against it. +/// +#pragma warning disable xUnit1045 // IStreamTransformer is not serializable but tests work fine +public class StreamTransformerCommonBehaviorTests +{ + public static TheoryData StreamTransformerTestCases() => + new() + { + { + "AgentBuilderStreamTransformer", + new AgentBuilderStreamTransformer(NullLogger.Instance), + // Agent Builder SSE format for conversation_id_set + """ + event: conversation_id_set + data: {"data":{"conversation_id":"360222c5-76aa-405a-8316-703e1061b621"}} + + event: message_chunk + data: {"data":{"text_chunk":"test"}} + + """ + }, + { + "LlmGatewayStreamTransformer", + new LlmGatewayStreamTransformer(NullLogger.Instance), + // LLM Gateway SSE format - minimal events + """ + event: agent_stream_output + data: [null, {"type":"ai_message_chunk","id":"1","timestamp":1234567890,"data":{"content":"test"}}] + + """ + } + }; + + [Theory] + [MemberData(nameof(StreamTransformerTestCases))] + public async Task TransformAsyncWhenConversationIdIsNullEmitsConversationStartEvent( + string transformerName, + IStreamTransformer transformer, + string sseData) + { + // Arrange + var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(sseData)); + + // Act - Pass null conversationId to simulate new conversation + var outputStream = await transformer.TransformAsync(inputStream, null, null, CancellationToken.None); + var events = await StreamTransformerTestHelpers.ParseAskAiEventsAsync(outputStream); + + // Assert - Should have ConversationStart event + events.Should().ContainSingle(e => e is AskAiEvent.ConversationStart, + $"{transformerName} should emit ConversationStart when conversationId is null"); + + var conversationStart = events.OfType().First(); + conversationStart.ConversationId.Should().NotBeNullOrEmpty( + $"{transformerName} should have a non-empty conversation ID in ConversationStart event"); + + // For LlmGateway, when conversationId is null, we generate a pure GUID + // For AgentBuilder, the conversation ID comes from the SSE event and may have a different format + if (transformerName == "LlmGatewayStreamTransformer") + { + Guid.TryParse(conversationStart.ConversationId, out _).Should().BeTrue( + $"{transformerName} should generate a valid GUID as conversation ID when conversationId is null"); + } } - private static async Task> ParseEventsFromStream(Stream stream) + [Theory] + [MemberData(nameof(StreamTransformerTestCases))] + public async Task TransformAsyncConversationStartEventHasValidTimestamp( + string transformerName, + IStreamTransformer transformer, + string sseData) { + // Arrange + var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(sseData)); + + // Act + var outputStream = await transformer.TransformAsync(inputStream, null, null, CancellationToken.None); var events = new List(); - - // Copy to memory stream to ensure all data is available - var ms = new MemoryStream(); - await stream.CopyToAsync(ms); - ms.Position = 0; - - using var reader = new StreamReader(ms, Encoding.UTF8); - - while (!reader.EndOfStream) + var reader = PipeReader.Create(outputStream); + await foreach (var sseEvent in SseParser.ParseAsync(reader, CancellationToken.None)) { - var line = await reader.ReadLineAsync(); - if (line == null) - break; + var evt = JsonSerializer.Deserialize(sseEvent.Data, AskAiEventJsonContext.Default.AskAiEvent); + if (evt != null) + events.Add(evt); + } - if (line.StartsWith("data: ", StringComparison.Ordinal)) - { - var json = line.Substring(6); - var evt = JsonSerializer.Deserialize(json, AskAiEventJsonContext.Default.AskAiEvent); - if (evt != null) - events.Add(evt); - } + // Assert + var conversationStart = events.OfType().FirstOrDefault(); + conversationStart.Should().NotBeNull( + $"{transformerName} should emit ConversationStart event"); + + conversationStart!.Timestamp.Should().BeGreaterThan(0, + $"{transformerName} ConversationStart should have a valid timestamp"); + } + + [Theory] + [MemberData(nameof(StreamTransformerTestCases))] + public async Task TransformAsyncConversationStartEventHasValidId( + string transformerName, + IStreamTransformer transformer, + string sseData) + { + // Arrange + var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(sseData)); + + // Act + var outputStream = await transformer.TransformAsync(inputStream, null, null, CancellationToken.None); + var events = new List(); + var reader = PipeReader.Create(outputStream); + await foreach (var sseEvent in SseParser.ParseAsync(reader, CancellationToken.None)) + { + var evt = JsonSerializer.Deserialize(sseEvent.Data, AskAiEventJsonContext.Default.AskAiEvent); + if (evt != null) + events.Add(evt); } - return events; + // Assert + var conversationStart = events.OfType().FirstOrDefault(); + conversationStart.Should().NotBeNull( + $"{transformerName} should emit ConversationStart event"); + + conversationStart!.Id.Should().NotBeNullOrEmpty( + $"{transformerName} ConversationStart should have a non-empty event ID"); } + } +#pragma warning restore xUnit1045 diff --git a/tests/Elastic.Documentation.Api.Infrastructure.Tests/Elastic.Documentation.Api.Infrastructure.Tests.csproj b/tests/Elastic.Documentation.Api.Infrastructure.Tests/Elastic.Documentation.Api.Infrastructure.Tests.csproj index 159c06712..7743c944a 100644 --- a/tests/Elastic.Documentation.Api.Infrastructure.Tests/Elastic.Documentation.Api.Infrastructure.Tests.csproj +++ b/tests/Elastic.Documentation.Api.Infrastructure.Tests/Elastic.Documentation.Api.Infrastructure.Tests.csproj @@ -4,10 +4,6 @@ net9.0 - - - -