diff --git a/tsp-output/@azure-tools/typespec-csharp/OpenAI.sln b/tsp-output/@azure-tools/typespec-csharp/OpenAI.sln
index 5d1edd8c3..e46a7dedf 100644
--- a/tsp-output/@azure-tools/typespec-csharp/OpenAI.sln
+++ b/tsp-output/@azure-tools/typespec-csharp/OpenAI.sln
@@ -1,10 +1,16 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.29709.97
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34701.34
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenAI", "src\OpenAI.csproj", "{28FF4005-4467-4E36-92E7-DEA27DEB1519}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenAI", "src\OpenAI.csproj", "{28FF4005-4467-4E36-92E7-DEA27DEB1519}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenAI.Tests", "tests\OpenAI.Tests.csproj", "{1F1CD1D4-9932-4B73-99D8-C252A67D4B46}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenAI.Tests", "tests\OpenAI.Tests.csproj", "{1F1CD1D4-9932-4B73-99D8-C252A67D4B46}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoApp", "app\DemoApp\DemoApp.csproj", "{79A5AD41-2A38-4771-93FE-0B3BCC6DB61A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureOpenAI", "azoai\AzureOpenAI\AzureOpenAI.csproj", "{FB61EB83-7171-48F4-9423-E64B99BDAA5A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ClientModel", "..\..\..\..\azure-sdk-for-net\sdk\core\System.ClientModel\src\System.ClientModel.csproj", "{8364F37B-2B3D-4CAA-8F20-5B808E19C6CE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -12,26 +18,6 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {B0C276D1-2930-4887-B29A-D1A33E7009A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B0C276D1-2930-4887-B29A-D1A33E7009A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B0C276D1-2930-4887-B29A-D1A33E7009A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B0C276D1-2930-4887-B29A-D1A33E7009A2}.Release|Any CPU.Build.0 = Release|Any CPU
- {8E9A77AC-792A-4432-8320-ACFD46730401}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8E9A77AC-792A-4432-8320-ACFD46730401}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8E9A77AC-792A-4432-8320-ACFD46730401}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8E9A77AC-792A-4432-8320-ACFD46730401}.Release|Any CPU.Build.0 = Release|Any CPU
- {A4241C1F-A53D-474C-9E4E-075054407E74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A4241C1F-A53D-474C-9E4E-075054407E74}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A4241C1F-A53D-474C-9E4E-075054407E74}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A4241C1F-A53D-474C-9E4E-075054407E74}.Release|Any CPU.Build.0 = Release|Any CPU
- {FA8BD3F1-8616-47B6-974C-7576CDF4717E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {FA8BD3F1-8616-47B6-974C-7576CDF4717E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {FA8BD3F1-8616-47B6-974C-7576CDF4717E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {FA8BD3F1-8616-47B6-974C-7576CDF4717E}.Release|Any CPU.Build.0 = Release|Any CPU
- {85677AD3-C214-42FA-AE6E-49B956CAC8DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {85677AD3-C214-42FA-AE6E-49B956CAC8DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {85677AD3-C214-42FA-AE6E-49B956CAC8DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {85677AD3-C214-42FA-AE6E-49B956CAC8DC}.Release|Any CPU.Build.0 = Release|Any CPU
{28FF4005-4467-4E36-92E7-DEA27DEB1519}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{28FF4005-4467-4E36-92E7-DEA27DEB1519}.Debug|Any CPU.Build.0 = Debug|Any CPU
{28FF4005-4467-4E36-92E7-DEA27DEB1519}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -40,6 +26,18 @@ Global
{1F1CD1D4-9932-4B73-99D8-C252A67D4B46}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1F1CD1D4-9932-4B73-99D8-C252A67D4B46}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1F1CD1D4-9932-4B73-99D8-C252A67D4B46}.Release|Any CPU.Build.0 = Release|Any CPU
+ {79A5AD41-2A38-4771-93FE-0B3BCC6DB61A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {79A5AD41-2A38-4771-93FE-0B3BCC6DB61A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {79A5AD41-2A38-4771-93FE-0B3BCC6DB61A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {79A5AD41-2A38-4771-93FE-0B3BCC6DB61A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FB61EB83-7171-48F4-9423-E64B99BDAA5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FB61EB83-7171-48F4-9423-E64B99BDAA5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FB61EB83-7171-48F4-9423-E64B99BDAA5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FB61EB83-7171-48F4-9423-E64B99BDAA5A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8364F37B-2B3D-4CAA-8F20-5B808E19C6CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8364F37B-2B3D-4CAA-8F20-5B808E19C6CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8364F37B-2B3D-4CAA-8F20-5B808E19C6CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8364F37B-2B3D-4CAA-8F20-5B808E19C6CE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/tsp-output/@azure-tools/typespec-csharp/app/DemoApp/DemoApp.csproj b/tsp-output/@azure-tools/typespec-csharp/app/DemoApp/DemoApp.csproj
new file mode 100644
index 000000000..4e4604be7
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/app/DemoApp/DemoApp.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+ latest
+
+
+
+
+
+
+
+
diff --git a/tsp-output/@azure-tools/typespec-csharp/app/DemoApp/Mocks/MockPipelineTransport.cs b/tsp-output/@azure-tools/typespec-csharp/app/DemoApp/Mocks/MockPipelineTransport.cs
new file mode 100644
index 000000000..1258c9d8a
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/app/DemoApp/Mocks/MockPipelineTransport.cs
@@ -0,0 +1,196 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.ClientModel;
+using System.ClientModel.Primitives;
+
+namespace ClientModel.Tests.Mocks;
+
+public class MockPipelineTransport : PipelineTransport
+{
+ private readonly Func _responseFactory;
+ private int _retryCount;
+
+ public string Id { get; }
+
+ public Action? OnSendingRequest { get; set; }
+ public Action? OnReceivedResponse { get; set; }
+
+ public MockPipelineTransport(string id, params int[] codes)
+ : this(id, i => (codes[i], BinaryData.FromString(string.Empty)))
+ {
+ }
+
+ public MockPipelineTransport(string id, Func responseFactory)
+ {
+ Id = id;
+ _responseFactory = responseFactory;
+ }
+
+ protected override PipelineMessage CreateMessageCore()
+ {
+ return new RetriableTransportMessage();
+ }
+
+ protected override void ProcessCore(PipelineMessage message)
+ {
+ try
+ {
+ Stamp(message, "Transport");
+
+ OnSendingRequest?.Invoke(_retryCount, message);
+
+ if (message is RetriableTransportMessage transportMessage)
+ {
+ (int status, BinaryData? content) = _responseFactory(_retryCount);
+ transportMessage.SetResponse(status, content);
+ }
+
+ OnReceivedResponse?.Invoke(_retryCount, message);
+ }
+ finally
+ {
+ _retryCount++;
+ }
+ }
+
+ protected override ValueTask ProcessCoreAsync(PipelineMessage message)
+ {
+ try
+ {
+ Stamp(message, "Transport");
+
+ OnSendingRequest?.Invoke(_retryCount, message);
+
+ if (message is RetriableTransportMessage transportMessage)
+ {
+ (int status, BinaryData? content) = _responseFactory(_retryCount);
+ transportMessage.SetResponse(status, content);
+ }
+
+ OnReceivedResponse?.Invoke(_retryCount, message);
+ }
+ finally
+ {
+ _retryCount++;
+ }
+
+ return new ValueTask();
+ }
+
+ private void Stamp(PipelineMessage message, string prefix)
+ {
+ List values;
+
+ if (message.TryGetProperty(typeof(ObservablePolicy), out object? prop) &&
+ prop is List list)
+ {
+ values = list;
+ }
+ else
+ {
+ values = new List();
+ message.SetProperty(typeof(ObservablePolicy), values);
+ }
+
+ values.Add($"{prefix}:{Id}");
+ }
+
+ private class RetriableTransportMessage : PipelineMessage
+ {
+ public RetriableTransportMessage() : this(new TransportRequest())
+ {
+ }
+
+ protected internal RetriableTransportMessage(PipelineRequest request) : base(request)
+ {
+ }
+
+ public void SetResponse(int status, BinaryData? content)
+ {
+ Response = new RetriableTransportResponse(status, content);
+ }
+ }
+
+ private class TransportRequest : PipelineRequest
+ {
+ private Uri? _uri;
+ private readonly PipelineRequestHeaders _headers;
+ private string _method;
+ private BinaryContent? _content;
+
+ public TransportRequest()
+ {
+ _headers = new MockRequestHeaders();
+ _uri = new Uri("https://www.example.com");
+ _method = "GET";
+ }
+
+ public override void Dispose() { }
+
+ protected override BinaryContent? ContentCore
+ {
+ get => _content;
+ set => _content = value;
+ }
+
+ protected override PipelineRequestHeaders HeadersCore
+ => _headers;
+
+ protected override string MethodCore
+ {
+ get => _method;
+ set => _method = value;
+ }
+
+ protected override Uri? UriCore
+ {
+ get => _uri;
+ set => _uri = value;
+ }
+ }
+
+ private class RetriableTransportResponse : PipelineResponse
+ {
+ private Stream? _contentStream;
+ private BinaryData _content;
+
+ public RetriableTransportResponse(int status, BinaryData? content)
+ {
+ Status = status;
+ ContentStream = content?.ToStream();
+ _content = content ?? BinaryData.FromString(string.Empty);
+ }
+
+ public override int Status { get; }
+
+ public override string ReasonPhrase => throw new NotImplementedException();
+
+ public override Stream? ContentStream
+ {
+ get => _contentStream;
+ set => _contentStream = value;
+ }
+
+ public override BinaryData Content => _content;
+
+ protected override PipelineResponseHeaders HeadersCore
+ => throw new NotImplementedException();
+
+ public override void Dispose() { }
+
+ public override BinaryData BufferContent(CancellationToken cancellationToken = default)
+ {
+ return _content = _contentStream == null ?
+ BinaryData.FromString(string.Empty) :
+ BinaryData.FromStream(_contentStream);
+ }
+
+ public override ValueTask BufferContentAsync(CancellationToken cancellationToken = default)
+ {
+ return new(_content = _contentStream == null ?
+ BinaryData.FromString(string.Empty) :
+ BinaryData.FromStream(_contentStream));
+ }
+ }
+}
diff --git a/tsp-output/@azure-tools/typespec-csharp/app/DemoApp/Mocks/MockRequestHeaders.cs b/tsp-output/@azure-tools/typespec-csharp/app/DemoApp/Mocks/MockRequestHeaders.cs
new file mode 100644
index 000000000..045e3a27e
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/app/DemoApp/Mocks/MockRequestHeaders.cs
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.ClientModel.Primitives;
+using System.Collections.Generic;
+
+namespace ClientModel.Tests.Mocks;
+
+public class MockRequestHeaders : PipelineRequestHeaders
+{
+ private readonly Dictionary _headers;
+
+ public MockRequestHeaders()
+ {
+ _headers = new Dictionary();
+ }
+
+ public override void Add(string name, string value)
+ {
+ if (_headers.ContainsKey(name))
+ {
+ _headers[name] += string.Concat(",", value);
+ }
+ else
+ {
+ _headers[name] = value;
+ }
+ }
+
+ public override IEnumerator> GetEnumerator()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override bool Remove(string name)
+ {
+ return _headers.Remove(name);
+ }
+
+ public override void Set(string name, string value)
+ {
+ _headers[name] = value;
+ }
+
+ public override bool TryGetValue(string name, out string? value)
+ {
+ return _headers.TryGetValue(name, out value);
+ }
+
+ public override bool TryGetValues(string name, out IEnumerable? values)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/tsp-output/@azure-tools/typespec-csharp/app/DemoApp/Mocks/ObservablePolicy.cs b/tsp-output/@azure-tools/typespec-csharp/app/DemoApp/Mocks/ObservablePolicy.cs
new file mode 100644
index 000000000..d70ea3b44
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/app/DemoApp/Mocks/ObservablePolicy.cs
@@ -0,0 +1,70 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.ClientModel.Primitives;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace ClientModel.Tests.Mocks;
+
+public class ObservablePolicy : PipelinePolicy
+{
+ public string Id { get; }
+ protected bool IsLastPolicy { get; set; } = false;
+
+ public ObservablePolicy(string id)
+ {
+ Id = id;
+ }
+
+ public override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex)
+ {
+ Stamp(message, "Request");
+
+ if (!IsLastPolicy)
+ {
+ ProcessNext(message, pipeline, currentIndex);
+ }
+
+ Stamp(message, "Response");
+ }
+
+ public override async ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex)
+ {
+ Stamp(message, "Request");
+
+ if (!IsLastPolicy)
+ {
+ await ProcessNextAsync(message, pipeline, currentIndex).ConfigureAwait(false);
+ }
+
+ Stamp(message, "Response");
+ }
+
+ private void Stamp(PipelineMessage message, string prefix)
+ {
+ List values;
+
+ if (message.TryGetProperty(typeof(ObservablePolicy), out object? prop) &&
+ prop is List list)
+ {
+ values = list;
+ }
+ else
+ {
+ values = new List();
+ message.SetProperty(typeof(ObservablePolicy), values);
+ }
+
+ values.Add($"{prefix}:{Id}");
+ }
+
+ public static List GetData(PipelineMessage message)
+ {
+ message.TryGetProperty(typeof(ObservablePolicy), out object? prop);
+
+ return prop is List list ? list : new List();
+ }
+
+ public override string ToString() => $"ObservablePolicy:{Id}";
+}
diff --git a/tsp-output/@azure-tools/typespec-csharp/app/DemoApp/Program.cs b/tsp-output/@azure-tools/typespec-csharp/app/DemoApp/Program.cs
new file mode 100644
index 000000000..d71844c34
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/app/DemoApp/Program.cs
@@ -0,0 +1,191 @@
+using AzureOpenAI;
+using AzureOpenAI.Models;
+using ClientModel.Tests.Mocks;
+using OpenAI;
+using OpenAI.Models;
+using System.ClientModel;
+
+Console.WriteLine("Hello, World!");
+
+//CallThirdPartyService();
+CallAzureService();
+
+void CallThirdPartyService()
+{
+ //
+ string apiKey = Environment.GetEnvironmentVariable("OPENAI_KEY")!;
+
+ OpenAIClient client = new(new Uri("https://www.mock-oai.com"), new ApiKeyCredential(apiKey), GetThirdPartyClientOptions());
+ //
+
+ Chat chatClient = client.GetChatClient();
+
+ List messages = new();
+ var message = new
+ {
+ role = "user",
+ content = "running over the same old ground"
+ };
+ messages.Add(BinaryData.FromObjectAsJson(message));
+
+ CreateChatCompletionRequest request = new(messages, CreateChatCompletionRequestModel.Gpt35Turbo);
+
+ ClientResult result = chatClient.CreateChatCompletion(request);
+ CreateChatCompletionResponse completion = result.Value;
+
+ // TODO: Do something with output
+}
+
+void CallAzureService()
+{
+ //
+ Uri endpoint = new("https://annelo-openai-01.openai.azure.com/");
+ string apiKey = "Fake"; // Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY")!;
+
+ AzureOpenAIClient client = new(endpoint, new ApiKeyCredential(apiKey), GetAzureClientOptions());
+ //
+
+ Chat chatClient = client.GetChatClient();
+
+ List messages = new();
+ var message = new
+ {
+ role = "user",
+ content = "running over the same old ground"
+ };
+ messages.Add(BinaryData.FromObjectAsJson(message));
+
+ CreateChatCompletionRequest request = new(messages, CreateChatCompletionRequestModel.Gpt35Turbo);
+
+ //
+
+ // Add Azure input property via extension methods
+ request.GetDataSources().Add(GetAzureSearchDataSource());
+
+ //
+ // request.DataSources.Add(GetAzureSearchDataSource());
+ //
+
+ //
+
+ ClientResult result = chatClient.CreateChatCompletion(request);
+
+ Console.WriteLine("**");
+
+ CreateChatCompletionResponse completion = result.Value;
+
+ //
+ // Use Azure output property via extension methods
+ AzureChatExtensionsMessageContext? azureContext = completion.Choices[0].Message.GetAzureExtensionsContext();
+
+ if (azureContext is not null && azureContext.Citations.Count > 0)
+ {
+ int i = 0;
+ foreach (var citation in azureContext.Citations)
+ {
+ Console.WriteLine($"Citation {i++}: Content={citation.Content}, Uri={citation.Url}, Title={citation.Title}");
+ }
+ }
+ //
+
+ // TODO: illustrate calls to protocol method overloads.
+}
+
+AzureSearchChatExtensionConfiguration GetAzureSearchDataSource()
+{
+ AzureSearchChatExtensionParameters searchParams = new(new Uri("https://azure.search.com"), "MySearchIndex");
+ return new(searchParams);
+}
+
+OpenAIClientOptions GetThirdPartyClientOptions()
+{
+ OpenAIClientOptions options = new()
+ {
+ Transport = new MockPipelineTransport("Transport", i => (200, BinaryData.FromString(
+ """
+ {
+ "id": "chatcmpl-7R1nGnsXO8n4oi9UPz2f3UHdgAYMn",
+ "created": 1686676106,
+ "choices": [
+ {
+ "index": 0,
+ "finish_reason": "stop",
+ "message": {
+ "role": "assistant",
+ "content": "Ahoy matey! So ye be wantin' to care for a fine squawkin' parrot, eh? Well, shiver me timbers, let ol' Cap'n Assistant share some wisdom with ye! Here be the steps to keepin' yer parrot happy 'n healthy:\n\n1. Secure a sturdy cage: Yer parrot be needin' a comfortable place to lay anchor! Be sure ye get a sturdy cage, at least double the size of the bird's wingspan, with enough space to spread their wings, yarrrr!\n\n2. Perches 'n toys: Aye, parrots need perches of different sizes, shapes, 'n textures to keep their feet healthy. Also, a few toys be helpin' to keep them entertained 'n their minds stimulated, arrrh!\n\n3. Proper grub: Feed yer feathered friend a balanced diet of high-quality pellets, fruits, 'n veggies to keep 'em strong 'n healthy. Give 'em fresh water every day, or ye’ll have a scurvy bird on yer hands!\n\n4. Cleanliness: Swab their cage deck! Clean their cage on a regular basis: fresh water 'n food daily, the floor every couple of days, 'n a thorough scrubbing ev'ry few weeks, so the bird be livin' in a tidy haven, arrhh!\n\n5. Socialize 'n train: Parrots be a sociable lot, arrr! Exercise 'n interact with 'em daily to create a bond 'n maintain their mental 'n physical health. Train 'em with positive reinforcement, treat 'em kindly, yarrr!\n\n6. Proper rest: Yer parrot be needin' ’bout 10-12 hours o' sleep each night. Cover their cage 'n let them slumber in a dim, quiet quarter for a proper night's rest, ye scallywag!\n\n7. Keep a weather eye open for illness: Birds be hidin' their ailments, arrr! Be watchful for signs of sickness, such as lethargy, loss of appetite, puffin' up, or change in droppings, and make haste to a vet if need be.\n\n8. Provide fresh air 'n avoid toxins: Parrots be sensitive to draft and pollutants. Keep yer quarters well ventilated, but no drafts, arrr! Be mindful of toxins like Teflon fumes, candles, or air fresheners.\n\nSo there ye have it, me hearty! With proper care 'n commitment, yer parrot will be squawkin' \"Yo-ho-ho\" for many years to come! Good luck, sailor, and may the wind be at yer back!"
+ },
+ "logprobs": null
+ }
+ ],
+ "usage": {
+ "completion_tokens": 557,
+ "prompt_tokens": 33,
+ "total_tokens": 590
+ }
+ }
+ """)))
+ };
+
+ return options;
+}
+
+
+AzureOpenAIClientOptions GetAzureClientOptions()
+{
+ MockPipelineTransport mockTransport = new("Transport", i => (200, BinaryData.FromString(
+ """
+ {
+ "id": "chatcmpl-7R1nGnsXO8n4oi9UPz2f3UHdgAYMn",
+ "created": 1686676106,
+ "choices": [
+ {
+ "index": 0,
+ "finish_reason": "stop",
+ "message": {
+ "role": "assistant",
+ "content": "Ahoy matey! So ye be wantin' to care for a fine squawkin' parrot, eh? Well, shiver me timbers, let ol' Cap'n Assistant share some wisdom with ye! Here be the steps to keepin' yer parrot happy 'n healthy:\n\n1. Secure a sturdy cage: Yer parrot be needin' a comfortable place to lay anchor! Be sure ye get a sturdy cage, at least double the size of the bird's wingspan, with enough space to spread their wings, yarrrr!\n\n2. Perches 'n toys: Aye, parrots need perches of different sizes, shapes, 'n textures to keep their feet healthy. Also, a few toys be helpin' to keep them entertained 'n their minds stimulated, arrrh!\n\n3. Proper grub: Feed yer feathered friend a balanced diet of high-quality pellets, fruits, 'n veggies to keep 'em strong 'n healthy. Give 'em fresh water every day, or ye’ll have a scurvy bird on yer hands!\n\n4. Cleanliness: Swab their cage deck! Clean their cage on a regular basis: fresh water 'n food daily, the floor every couple of days, 'n a thorough scrubbing ev'ry few weeks, so the bird be livin' in a tidy haven, arrhh!\n\n5. Socialize 'n train: Parrots be a sociable lot, arrr! Exercise 'n interact with 'em daily to create a bond 'n maintain their mental 'n physical health. Train 'em with positive reinforcement, treat 'em kindly, yarrr!\n\n6. Proper rest: Yer parrot be needin' ’bout 10-12 hours o' sleep each night. Cover their cage 'n let them slumber in a dim, quiet quarter for a proper night's rest, ye scallywag!\n\n7. Keep a weather eye open for illness: Birds be hidin' their ailments, arrr! Be watchful for signs of sickness, such as lethargy, loss of appetite, puffin' up, or change in droppings, and make haste to a vet if need be.\n\n8. Provide fresh air 'n avoid toxins: Parrots be sensitive to draft and pollutants. Keep yer quarters well ventilated, but no drafts, arrr! Be mindful of toxins like Teflon fumes, candles, or air fresheners.\n\nSo there ye have it, me hearty! With proper care 'n commitment, yer parrot will be squawkin' \"Yo-ho-ho\" for many years to come! Good luck, sailor, and may the wind be at yer back!",
+ "context": {
+ "citations": [
+ {
+ "content": "Content of the citation",
+ "url": "https://www.example.com",
+ "title": "Title of the citation",
+ "filepath": "path/to/file",
+ "chunk_id": "chunk-id"
+ }
+ ]
+ }
+ },
+ "logprobs": null
+ }
+ ],
+ "usage": {
+ "completion_tokens": 557,
+ "prompt_tokens": 33,
+ "total_tokens": 590
+ }
+ }
+ """)));
+
+ mockTransport.OnSendingRequest = (i, m) =>
+ {
+ Console.WriteLine("Request:");
+ Console.WriteLine($" Uri='{m.Request.Uri}'");
+ Console.WriteLine($" Content='{WriteAsString(m.Request.Content!)}'");
+ };
+
+ AzureOpenAIClientOptions options = new()
+ {
+ Transport = mockTransport
+ };
+
+ return options;
+}
+
+string WriteAsString(BinaryContent content)
+{
+ MemoryStream stream = new();
+ content.WriteTo(stream);
+ stream.Position = 0;
+ return BinaryData.FromStream(stream).ToString();
+}
\ No newline at end of file
diff --git a/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/AzureChatClient.cs b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/AzureChatClient.cs
new file mode 100644
index 000000000..fe8ff22be
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/AzureChatClient.cs
@@ -0,0 +1,107 @@
+using AzureOpenAI.Models;
+using OpenAI;
+using OpenAI.Models;
+using System.ClientModel;
+using System.ClientModel.Primitives;
+
+namespace AzureOpenAI;
+
+internal class AzureChatClient : Chat
+{
+ private readonly string _apiVersion;
+ private readonly Uri _endpoint;
+
+ internal AzureChatClient(ClientPipeline pipeline, ApiKeyCredential credential, Uri endpoint, string apiVersion)
+ : base(pipeline, credential, endpoint)
+ {
+ _apiVersion = apiVersion;
+ _endpoint = endpoint;
+ }
+
+ public override async Task> CreateChatCompletionAsync(CreateChatCompletionRequest createChatCompletionRequest, CancellationToken cancellationToken = default)
+ {
+ Argument.AssertNotNull(createChatCompletionRequest, nameof(createChatCompletionRequest));
+
+ using BinaryContent content = createChatCompletionRequest.ToBinaryContent();
+ ClientResult result = await CreateChatCompletionAsync(createChatCompletionRequest.Model.ToString(), content, context: default).ConfigureAwait(false);
+ var value = ModelReaderWriter.Read(result.GetRawResponse().Content)!;
+ return ClientResult.FromValue(value, result.GetRawResponse());
+ }
+
+ public override ClientResult CreateChatCompletion(CreateChatCompletionRequest createChatCompletionRequest, CancellationToken cancellationToken = default)
+ {
+ Argument.AssertNotNull(createChatCompletionRequest, nameof(createChatCompletionRequest));
+
+ using BinaryContent content = createChatCompletionRequest.ToBinaryContent();
+ ClientResult result = CreateChatCompletion(createChatCompletionRequest.Model.ToString(), content, context: default);
+ var value = ModelReaderWriter.Read(result.GetRawResponse().Content)!;
+ return ClientResult.FromValue(value, result.GetRawResponse());
+ }
+
+ public override Task CreateChatCompletionAsync(BinaryContent content, RequestOptions context = null)
+ {
+ // Note, that we can later remap the values from the 3rd party client format to the
+ // Azure client format, but this has a perf cost and it's an improvement that can
+ // come later.
+ throw new InvalidOperationException("Improperly formatted content -- Azure service requires different format. " +
+ $"Please consult the REST API documentation and call the '{nameof(CreateChatCompletion)}' method overload that " +
+ "takes a 'model' parameter.");
+ }
+
+ public override ClientResult CreateChatCompletion(BinaryContent content, RequestOptions context = null)
+ {
+ throw new InvalidOperationException("Improperly formatted content -- Azure service requires different format. " +
+ $"Please consult the REST API documentation and call the '{nameof(CreateChatCompletion)}' method overload that " +
+ "takes a 'model' parameter.");
+ }
+
+ public ClientResult CreateChatCompletion(string model, BinaryContent content, RequestOptions context = null)
+ {
+ Argument.AssertNotNull(model, nameof(model));
+ Argument.AssertNotNull(content, nameof(content));
+
+ using PipelineMessage message = CreateCreateChatCompletionRequest(model, content, context);
+ return ClientResult.FromResponse(Pipeline.ProcessMessage(message, context));
+ }
+
+ public async Task CreateChatCompletionAsync(string model, BinaryContent content, RequestOptions context = null)
+ {
+ Argument.AssertNotNull(model, nameof(model));
+ Argument.AssertNotNull(content, nameof(content));
+
+ using PipelineMessage message = CreateCreateChatCompletionRequest(model, content, context);
+ return ClientResult.FromResponse(await Pipeline.ProcessMessageAsync(message, context).ConfigureAwait(false));
+ }
+
+ private PipelineMessage CreateCreateChatCompletionRequest(string model, BinaryContent content, RequestOptions context)
+ {
+ var message = Pipeline.CreateMessage();
+ message.ResponseClassifier = PipelineMessageClassifier200;
+
+ var request = message.Request;
+ request.Method = "POST";
+
+ var uri = new ClientUriBuilder();
+ uri.Reset(_endpoint);
+ uri.AppendPath("/openai/deployments/", false);
+ uri.AppendPath(model, false);
+ uri.AppendPath("/chat/completions", false);
+ uri.AppendQuery("api-version", _apiVersion, true);
+ request.Uri = uri.ToUri();
+
+ request.Headers.Set("Accept", "application/json");
+ request.Headers.Set("Content-Type", "application/json");
+
+ request.Content = content;
+
+ if (context != null)
+ {
+ message.Apply(context);
+ }
+
+ return message;
+ }
+
+ private static PipelineMessageClassifier? _pipelineMessageClassifier200;
+ private static PipelineMessageClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 });
+}
diff --git a/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/AzureOpenAI.csproj b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/AzureOpenAI.csproj
new file mode 100644
index 000000000..460feb8c7
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/AzureOpenAI.csproj
@@ -0,0 +1,14 @@
+
+
+
+ netstandard2.0
+ enable
+ enable
+ latest
+
+
+
+
+
+
+
diff --git a/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/AzureOpenAIClient.cs b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/AzureOpenAIClient.cs
new file mode 100644
index 000000000..81ea55bb2
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/AzureOpenAIClient.cs
@@ -0,0 +1,26 @@
+using OpenAI;
+using System.ClientModel;
+
+namespace AzureOpenAI;
+
+public class AzureOpenAIClient : OpenAIClient
+{
+ private readonly string _apiVersion;
+ private readonly ApiKeyCredential _apiKeyCredential;
+ private readonly Uri _endpoint;
+
+ public AzureOpenAIClient(Uri endpoint, ApiKeyCredential credential, AzureOpenAIClientOptions? options = default)
+ : base(endpoint, credential, options)
+ {
+ options ??= new AzureOpenAIClientOptions();
+
+ _apiVersion = options.ApiVersion;
+ _endpoint = endpoint;
+ _apiKeyCredential = credential;
+ }
+
+ public override Chat GetChatClient()
+ {
+ return new AzureChatClient(Pipeline, _apiKeyCredential, _endpoint, _apiVersion);
+ }
+}
diff --git a/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/AzureOpenAIClientOptions.cs b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/AzureOpenAIClientOptions.cs
new file mode 100644
index 000000000..23cc45ae3
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/AzureOpenAIClientOptions.cs
@@ -0,0 +1,13 @@
+using OpenAI;
+
+namespace AzureOpenAI;
+
+public class AzureOpenAIClientOptions : OpenAIClientOptions
+{
+ public AzureOpenAIClientOptions(string? version = default)
+ {
+ ApiVersion = version ?? "1.0";
+ }
+
+ public string ApiVersion { get; set; }
+}
diff --git a/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/ChatClientExtensions.cs b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/ChatClientExtensions.cs
new file mode 100644
index 000000000..0bef56f06
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/ChatClientExtensions.cs
@@ -0,0 +1,29 @@
+using OpenAI;
+using System.ClientModel;
+using System.ClientModel.Primitives;
+
+namespace AzureOpenAI;
+
+public static class ChatClientExtensions
+{
+ // Expose overloads of protocol methods as extensions to the base client
+ public static ClientResult CreateChatCompletion(this Chat client, string model, BinaryContent content, RequestOptions options = default)
+ {
+ if (client is not AzureChatClient azureClient)
+ {
+ throw new NotSupportedException("Cannot call CreateChatCompletion with 'model' parameter when not using the Azure OpeAI client.");
+ }
+
+ return azureClient.CreateChatCompletion(model, content, options);
+ }
+
+ public static async Task CreateChatCompletionAsync(this Chat client, string model, BinaryContent content, RequestOptions options = default)
+ {
+ if (client is not AzureChatClient azureClient)
+ {
+ throw new NotSupportedException("Cannot call CreateChatCompletionAsync with 'model' parameter when not using the Azure OpeAI client.");
+ }
+
+ return await azureClient.CreateChatCompletionAsync(model, content, options).ConfigureAwait(false);
+ }
+}
diff --git a/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/Argument.cs b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/Argument.cs
new file mode 100644
index 000000000..b39f2be32
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/Argument.cs
@@ -0,0 +1,125 @@
+//
+
+#nullable disable
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace AzureOpenAI;
+
+internal static class Argument
+{
+ public static void AssertNotNull(T value, string name)
+ {
+ if (value is null)
+ {
+ throw new ArgumentNullException(name);
+ }
+ }
+
+ public static void AssertNotNull(T? value, string name)
+ where T : struct
+ {
+ if (!value.HasValue)
+ {
+ throw new ArgumentNullException(name);
+ }
+ }
+
+ public static void AssertNotNullOrEmpty(IEnumerable value, string name)
+ {
+ if (value is null)
+ {
+ throw new ArgumentNullException(name);
+ }
+ if (value is ICollection collectionOfT && collectionOfT.Count == 0)
+ {
+ throw new ArgumentException("Value cannot be an empty collection.", name);
+ }
+ if (value is ICollection collection && collection.Count == 0)
+ {
+ throw new ArgumentException("Value cannot be an empty collection.", name);
+ }
+ using IEnumerator e = value.GetEnumerator();
+ if (!e.MoveNext())
+ {
+ throw new ArgumentException("Value cannot be an empty collection.", name);
+ }
+ }
+
+ public static void AssertNotNullOrEmpty(string value, string name)
+ {
+ if (value is null)
+ {
+ throw new ArgumentNullException(name);
+ }
+ if (value.Length == 0)
+ {
+ throw new ArgumentException("Value cannot be an empty string.", name);
+ }
+ }
+
+ public static void AssertNotNullOrWhiteSpace(string value, string name)
+ {
+ if (value is null)
+ {
+ throw new ArgumentNullException(name);
+ }
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ throw new ArgumentException("Value cannot be empty or contain only white-space characters.", name);
+ }
+ }
+
+ public static void AssertNotDefault(ref T value, string name)
+ where T : struct, IEquatable
+ {
+ if (value.Equals(default))
+ {
+ throw new ArgumentException("Value cannot be empty.", name);
+ }
+ }
+
+ public static void AssertInRange(T value, T minimum, T maximum, string name)
+ where T : notnull, IComparable
+ {
+ if (minimum.CompareTo(value) > 0)
+ {
+ throw new ArgumentOutOfRangeException(name, "Value is less than the minimum allowed.");
+ }
+ if (maximum.CompareTo(value) < 0)
+ {
+ throw new ArgumentOutOfRangeException(name, "Value is greater than the maximum allowed.");
+ }
+ }
+
+ public static void AssertEnumDefined(Type enumType, object value, string name)
+ {
+ if (!Enum.IsDefined(enumType, value))
+ {
+ throw new ArgumentException($"Value not defined for {enumType.FullName}.", name);
+ }
+ }
+
+ public static T CheckNotNull(T value, string name)
+ where T : class
+ {
+ AssertNotNull(value, name);
+ return value;
+ }
+
+ public static string CheckNotNullOrEmpty(string value, string name)
+ {
+ AssertNotNullOrEmpty(value, name);
+ return value;
+ }
+
+ public static void AssertNull(T value, string name, string message = null)
+ {
+ if (value != null)
+ {
+ throw new ArgumentException(message ?? "Value must be null.", name);
+ }
+ }
+}
diff --git a/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/ChangeTrackingDictionary.cs b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/ChangeTrackingDictionary.cs
new file mode 100644
index 000000000..b054e615b
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/ChangeTrackingDictionary.cs
@@ -0,0 +1,163 @@
+//
+
+#nullable disable
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace AzureOpenAI;
+
+internal class ChangeTrackingDictionary : IDictionary, IReadOnlyDictionary where TKey : notnull
+{
+ private IDictionary _innerDictionary;
+
+ public ChangeTrackingDictionary()
+ {
+ }
+
+ public ChangeTrackingDictionary(IDictionary dictionary)
+ {
+ if (dictionary == null)
+ {
+ return;
+ }
+ _innerDictionary = new Dictionary(dictionary);
+ }
+
+ public ChangeTrackingDictionary(IReadOnlyDictionary dictionary)
+ {
+ if (dictionary == null)
+ {
+ return;
+ }
+ _innerDictionary = new Dictionary();
+ foreach (var pair in dictionary)
+ {
+ _innerDictionary.Add(pair);
+ }
+ }
+
+ public bool IsUndefined => _innerDictionary == null;
+
+ public int Count => IsUndefined ? 0 : EnsureDictionary().Count;
+
+ public bool IsReadOnly => IsUndefined ? false : EnsureDictionary().IsReadOnly;
+
+ public ICollection Keys => IsUndefined ? Array.Empty() : EnsureDictionary().Keys;
+
+ public ICollection Values => IsUndefined ? Array.Empty() : EnsureDictionary().Values;
+
+ public TValue this[TKey key]
+ {
+ get
+ {
+ if (IsUndefined)
+ {
+ throw new KeyNotFoundException(nameof(key));
+ }
+ return EnsureDictionary()[key];
+ }
+ set
+ {
+ EnsureDictionary()[key] = value;
+ }
+ }
+
+ IEnumerable IReadOnlyDictionary.Keys => Keys;
+
+ IEnumerable IReadOnlyDictionary.Values => Values;
+
+ public IEnumerator> GetEnumerator()
+ {
+ if (IsUndefined)
+ {
+ IEnumerator> enumerateEmpty()
+ {
+ yield break;
+ }
+ return enumerateEmpty();
+ }
+ return EnsureDictionary().GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public void Add(KeyValuePair item)
+ {
+ EnsureDictionary().Add(item);
+ }
+
+ public void Clear()
+ {
+ EnsureDictionary().Clear();
+ }
+
+ public bool Contains(KeyValuePair item)
+ {
+ if (IsUndefined)
+ {
+ return false;
+ }
+ return EnsureDictionary().Contains(item);
+ }
+
+ public void CopyTo(KeyValuePair[] array, int index)
+ {
+ if (IsUndefined)
+ {
+ return;
+ }
+ EnsureDictionary().CopyTo(array, index);
+ }
+
+ public bool Remove(KeyValuePair item)
+ {
+ if (IsUndefined)
+ {
+ return false;
+ }
+ return EnsureDictionary().Remove(item);
+ }
+
+ public void Add(TKey key, TValue value)
+ {
+ EnsureDictionary().Add(key, value);
+ }
+
+ public bool ContainsKey(TKey key)
+ {
+ if (IsUndefined)
+ {
+ return false;
+ }
+ return EnsureDictionary().ContainsKey(key);
+ }
+
+ public bool Remove(TKey key)
+ {
+ if (IsUndefined)
+ {
+ return false;
+ }
+ return EnsureDictionary().Remove(key);
+ }
+
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ if (IsUndefined)
+ {
+ value = default;
+ return false;
+ }
+ return EnsureDictionary().TryGetValue(key, out value);
+ }
+
+ public IDictionary EnsureDictionary()
+ {
+ return _innerDictionary ??= new Dictionary();
+ }
+}
diff --git a/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/ChangeTrackingList.cs b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/ChangeTrackingList.cs
new file mode 100644
index 000000000..a2ff13738
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/ChangeTrackingList.cs
@@ -0,0 +1,149 @@
+//
+
+#nullable disable
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace AzureOpenAI;
+
+internal class ChangeTrackingList : IList, IReadOnlyList
+{
+ private IList _innerList;
+
+ public ChangeTrackingList()
+ {
+ }
+
+ public ChangeTrackingList(IList innerList)
+ {
+ if (innerList != null)
+ {
+ _innerList = innerList;
+ }
+ }
+
+ public ChangeTrackingList(IReadOnlyList innerList)
+ {
+ if (innerList != null)
+ {
+ _innerList = innerList.ToList();
+ }
+ }
+
+ public bool IsUndefined => _innerList == null;
+
+ public int Count => IsUndefined ? 0 : EnsureList().Count;
+
+ public bool IsReadOnly => IsUndefined ? false : EnsureList().IsReadOnly;
+
+ public T this[int index]
+ {
+ get
+ {
+ if (IsUndefined)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ return EnsureList()[index];
+ }
+ set
+ {
+ if (IsUndefined)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ EnsureList()[index] = value;
+ }
+ }
+
+ public void Reset()
+ {
+ _innerList = null;
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ if (IsUndefined)
+ {
+ IEnumerator enumerateEmpty()
+ {
+ yield break;
+ }
+ return enumerateEmpty();
+ }
+ return EnsureList().GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public void Add(T item)
+ {
+ EnsureList().Add(item);
+ }
+
+ public void Clear()
+ {
+ EnsureList().Clear();
+ }
+
+ public bool Contains(T item)
+ {
+ if (IsUndefined)
+ {
+ return false;
+ }
+ return EnsureList().Contains(item);
+ }
+
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ if (IsUndefined)
+ {
+ return;
+ }
+ EnsureList().CopyTo(array, arrayIndex);
+ }
+
+ public bool Remove(T item)
+ {
+ if (IsUndefined)
+ {
+ return false;
+ }
+ return EnsureList().Remove(item);
+ }
+
+ public int IndexOf(T item)
+ {
+ if (IsUndefined)
+ {
+ return -1;
+ }
+ return EnsureList().IndexOf(item);
+ }
+
+ public void Insert(int index, T item)
+ {
+ EnsureList().Insert(index, item);
+ }
+
+ public void RemoveAt(int index)
+ {
+ if (IsUndefined)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ EnsureList().RemoveAt(index);
+ }
+
+ public IList EnsureList()
+ {
+ return _innerList ??= new List();
+ }
+}
diff --git a/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/ClientPipelineExtensions.cs b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/ClientPipelineExtensions.cs
new file mode 100644
index 000000000..356acda94
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/ClientPipelineExtensions.cs
@@ -0,0 +1,49 @@
+//
+
+#nullable disable
+
+using System;
+using System.ClientModel;
+using System.ClientModel.Primitives;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace OpenAI
+{
+ internal static class ClientPipelineExtensions
+ {
+ public static async ValueTask ProcessMessageAsync(this ClientPipeline pipeline, PipelineMessage message, RequestOptions requestContext, CancellationToken cancellationToken = default)
+ {
+ await pipeline.SendAsync(message).ConfigureAwait(false);
+
+ if (message.Response == null)
+ {
+ throw new InvalidOperationException("Failed to receive Result.");
+ }
+
+ if (!message.Response.IsError || requestContext?.ErrorOptions == ClientErrorBehaviors.NoThrow)
+ {
+ return message.Response;
+ }
+
+ throw new ClientResultException(message.Response);
+ }
+
+ public static PipelineResponse ProcessMessage(this ClientPipeline pipeline, PipelineMessage message, RequestOptions requestContext, CancellationToken cancellationToken = default)
+ {
+ pipeline.Send(message);
+
+ if (message.Response == null)
+ {
+ throw new InvalidOperationException("Failed to receive Result.");
+ }
+
+ if (!message.Response.IsError || requestContext?.ErrorOptions == ClientErrorBehaviors.NoThrow)
+ {
+ return message.Response;
+ }
+
+ throw new ClientResultException(message.Response);
+ }
+ }
+}
diff --git a/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/ClientUriBuilder.cs b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/ClientUriBuilder.cs
new file mode 100644
index 000000000..782d0be04
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/ClientUriBuilder.cs
@@ -0,0 +1,209 @@
+//
+
+#nullable disable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace AzureOpenAI;
+
+internal class ClientUriBuilder
+{
+ private UriBuilder _uriBuilder;
+ private StringBuilder _pathBuilder;
+ private StringBuilder _queryBuilder;
+ private const char PathSeparator = '/';
+
+ public ClientUriBuilder()
+ {
+ }
+
+ private UriBuilder UriBuilder => _uriBuilder ??= new UriBuilder();
+
+ private StringBuilder PathBuilder => _pathBuilder ??= new StringBuilder(UriBuilder.Path);
+
+ private StringBuilder QueryBuilder => _queryBuilder ??= new StringBuilder(UriBuilder.Query);
+
+ public void Reset(Uri uri)
+ {
+ _uriBuilder = new UriBuilder(uri);
+ _pathBuilder = new StringBuilder(UriBuilder.Path);
+ _queryBuilder = new StringBuilder(UriBuilder.Query);
+ }
+
+ public void AppendPath(string value, bool escape)
+ {
+ Argument.AssertNotNullOrWhiteSpace(value, nameof(value));
+
+ if (escape)
+ {
+ value = Uri.EscapeDataString(value);
+ }
+
+ if (value[0] == PathSeparator)
+ {
+ value = value.Substring(1);
+ }
+
+ PathBuilder.Append(value);
+ UriBuilder.Path = PathBuilder.ToString();
+ }
+
+ public void AppendPath(bool value, bool escape = false)
+ {
+ AppendPath(ModelSerializationExtensions.TypeFormatters.ConvertToString(value), escape);
+ }
+
+ public void AppendPath(float value, bool escape = true)
+ {
+ AppendPath(ModelSerializationExtensions.TypeFormatters.ConvertToString(value), escape);
+ }
+
+ public void AppendPath(double value, bool escape = true)
+ {
+ AppendPath(ModelSerializationExtensions.TypeFormatters.ConvertToString(value), escape);
+ }
+
+ public void AppendPath(int value, bool escape = true)
+ {
+ AppendPath(ModelSerializationExtensions.TypeFormatters.ConvertToString(value), escape);
+ }
+
+ public void AppendPath(byte[] value, string format, bool escape = true)
+ {
+ AppendPath(ModelSerializationExtensions.TypeFormatters.ConvertToString(value, format), escape);
+ }
+
+ public void AppendPath(IEnumerable value, bool escape = true)
+ {
+ AppendPath(ModelSerializationExtensions.TypeFormatters.ConvertToString(value), escape);
+ }
+
+ public void AppendPath(DateTimeOffset value, string format, bool escape = true)
+ {
+ AppendPath(ModelSerializationExtensions.TypeFormatters.ConvertToString(value, format), escape);
+ }
+
+ public void AppendPath(TimeSpan value, string format, bool escape = true)
+ {
+ AppendPath(ModelSerializationExtensions.TypeFormatters.ConvertToString(value, format), escape);
+ }
+
+ public void AppendPath(Guid value, bool escape = true)
+ {
+ AppendPath(ModelSerializationExtensions.TypeFormatters.ConvertToString(value), escape);
+ }
+
+ public void AppendPath(long value, bool escape = true)
+ {
+ AppendPath(ModelSerializationExtensions.TypeFormatters.ConvertToString(value), escape);
+ }
+
+ public void AppendQuery(string name, string value, bool escape)
+ {
+ Argument.AssertNotNullOrWhiteSpace(name, nameof(name));
+ Argument.AssertNotNullOrWhiteSpace(value, nameof(value));
+
+ if (QueryBuilder.Length == 0)
+ {
+ QueryBuilder.Append('?');
+ }
+ else
+ {
+ QueryBuilder.Append('&');
+ }
+
+ if (escape)
+ {
+ value = Uri.EscapeDataString(value);
+ }
+
+ QueryBuilder.Append(name);
+ QueryBuilder.Append('=');
+ QueryBuilder.Append(value);
+ }
+
+ public void AppendQuery(string name, bool value, bool escape = false)
+ {
+ AppendQuery(name, ModelSerializationExtensions.TypeFormatters.ConvertToString(value), escape);
+ }
+
+ public void AppendQuery(string name, float value, bool escape = true)
+ {
+ AppendQuery(name, ModelSerializationExtensions.TypeFormatters.ConvertToString(value), escape);
+ }
+
+ public void AppendQuery(string name, DateTimeOffset value, string format, bool escape = true)
+ {
+ AppendQuery(name, ModelSerializationExtensions.TypeFormatters.ConvertToString(value, format), escape);
+ }
+
+ public void AppendQuery(string name, TimeSpan value, string format, bool escape = true)
+ {
+ AppendQuery(name, ModelSerializationExtensions.TypeFormatters.ConvertToString(value, format), escape);
+ }
+
+ public void AppendQuery(string name, double value, bool escape = true)
+ {
+ AppendQuery(name, ModelSerializationExtensions.TypeFormatters.ConvertToString(value), escape);
+ }
+
+ public void AppendQuery(string name, decimal value, bool escape = true)
+ {
+ AppendQuery(name, ModelSerializationExtensions.TypeFormatters.ConvertToString(value), escape);
+ }
+
+ public void AppendQuery(string name, int value, bool escape = true)
+ {
+ AppendQuery(name, ModelSerializationExtensions.TypeFormatters.ConvertToString(value), escape);
+ }
+
+ public void AppendQuery(string name, long value, bool escape = true)
+ {
+ AppendQuery(name, ModelSerializationExtensions.TypeFormatters.ConvertToString(value), escape);
+ }
+
+ public void AppendQuery(string name, TimeSpan value, bool escape = true)
+ {
+ AppendQuery(name, ModelSerializationExtensions.TypeFormatters.ConvertToString(value), escape);
+ }
+
+ public void AppendQuery(string name, byte[] value, string format, bool escape = true)
+ {
+ AppendQuery(name, ModelSerializationExtensions.TypeFormatters.ConvertToString(value, format), escape);
+ }
+
+ public void AppendQuery(string name, Guid value, bool escape = true)
+ {
+ AppendQuery(name, ModelSerializationExtensions.TypeFormatters.ConvertToString(value), escape);
+ }
+
+ public void AppendQueryDelimited(string name, IEnumerable value, string delimiter, bool escape = true)
+ {
+ var stringValues = value.Select(v => ModelSerializationExtensions.TypeFormatters.ConvertToString(v));
+ AppendQuery(name, string.Join(delimiter, stringValues), escape);
+ }
+
+ public void AppendQueryDelimited(string name, IEnumerable value, string delimiter, string format, bool escape = true)
+ {
+ var stringValues = value.Select(v => ModelSerializationExtensions.TypeFormatters.ConvertToString(v, format));
+ AppendQuery(name, string.Join(delimiter, stringValues), escape);
+ }
+
+ public Uri ToUri()
+ {
+ if (_pathBuilder != null)
+ {
+ UriBuilder.Path = _pathBuilder.ToString();
+ }
+
+ if (_queryBuilder != null)
+ {
+ UriBuilder.Query = _queryBuilder.ToString();
+ }
+
+ return UriBuilder.Uri;
+ }
+}
diff --git a/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/JsonModelList.cs b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/JsonModelList.cs
new file mode 100644
index 000000000..eac0fd6c8
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/JsonModelList.cs
@@ -0,0 +1,89 @@
+using System.ClientModel.Primitives;
+using System.Text.Json;
+
+namespace AzureOpenAI;
+
+internal class JsonModelList : List, IJsonModel>
+ where TModel : IJsonModel
+{
+ public JsonModelList Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options)
+ {
+ var format = options.Format == "W" ? ((IPersistableModel>)this).GetFormatFromOptions(options) : options.Format;
+ if (format != "J")
+ {
+ throw new FormatException($"The model {nameof(JsonModelList)} does not support reading '{format}' format.");
+ }
+
+ using JsonDocument document = JsonDocument.ParseValue(ref reader);
+ return DeserializeJsonModelList(document.RootElement, options);
+ }
+
+ public JsonModelList Create(BinaryData data, ModelReaderWriterOptions options)
+ {
+ var format = options.Format == "W" ? ((IPersistableModel>)this).GetFormatFromOptions(options) : options.Format;
+
+ switch (format)
+ {
+ case "J":
+ {
+ using JsonDocument document = JsonDocument.Parse(data);
+ return DeserializeJsonModelList(document.RootElement, options);
+ }
+ default:
+ throw new FormatException($"The model {nameof(JsonModelList)} does not support reading '{options.Format}' format.");
+ }
+ }
+
+ internal static JsonModelList DeserializeJsonModelList(JsonElement element, ModelReaderWriterOptions options = null)
+ {
+ options ??= new ModelReaderWriterOptions("W");
+
+ if (element.ValueKind != JsonValueKind.Array)
+ {
+ throw new InvalidOperationException("Cannot deserialize JsonModelList from JSON that is not an array.");
+ }
+
+ JsonModelList list = [];
+
+ foreach (JsonElement item in element.EnumerateArray())
+ {
+ // TODO: Make efficient
+ TModel? value = ModelReaderWriter.Read(BinaryData.FromString(item.ToString()), options) ??
+ throw new InvalidOperationException("Failed to deserialized array element.");
+ list.Add(value);
+ }
+
+ return list;
+ }
+
+ public string GetFormatFromOptions(ModelReaderWriterOptions options) => "J";
+
+ public void Write(Utf8JsonWriter writer, ModelReaderWriterOptions options)
+ {
+ var format = options.Format == "W" ? ((IPersistableModel>)this).GetFormatFromOptions(options) : options.Format;
+ if (format != "J")
+ {
+ throw new FormatException($"The model {nameof(JsonModelList)} does not support writing '{format}' format.");
+ }
+
+ writer.WriteStartArray();
+
+ foreach (IJsonModel item in this)
+ {
+ item.Write(writer, options);
+ }
+
+ writer.WriteEndArray();
+ }
+
+ public BinaryData Write(ModelReaderWriterOptions options)
+ {
+ var format = options.Format == "W" ? ((IPersistableModel>)this).GetFormatFromOptions(options) : options.Format;
+
+ return format switch
+ {
+ "J" => ModelReaderWriter.Write(this, options),
+ _ => throw new FormatException($"The model {nameof(JsonModelList)} does not support writing '{options.Format}' format."),
+ };
+ }
+}
\ No newline at end of file
diff --git a/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/ModelSerializationExtensions.cs b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/ModelSerializationExtensions.cs
new file mode 100644
index 000000000..e4edf1684
--- /dev/null
+++ b/tsp-output/@azure-tools/typespec-csharp/azoai/AzureOpenAI/Generated/Internal/ModelSerializationExtensions.cs
@@ -0,0 +1,388 @@
+//
+
+#nullable disable
+
+using System;
+using System.ClientModel.Primitives;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Text.Json;
+using System.Xml;
+
+namespace AzureOpenAI;
+
+internal static class ModelSerializationExtensions
+{
+ public static object GetObject(this JsonElement element)
+ {
+ switch (element.ValueKind)
+ {
+ case JsonValueKind.String:
+ return element.GetString();
+ case JsonValueKind.Number:
+ if (element.TryGetInt32(out int intValue))
+ {
+ return intValue;
+ }
+ if (element.TryGetInt64(out long longValue))
+ {
+ return longValue;
+ }
+ return element.GetDouble();
+ case JsonValueKind.True:
+ return true;
+ case JsonValueKind.False:
+ return false;
+ case JsonValueKind.Undefined:
+ case JsonValueKind.Null:
+ return null;
+ case JsonValueKind.Object:
+ var dictionary = new Dictionary();
+ foreach (var jsonProperty in element.EnumerateObject())
+ {
+ dictionary.Add(jsonProperty.Name, jsonProperty.Value.GetObject());
+ }
+ return dictionary;
+ case JsonValueKind.Array:
+ var list = new List