openai/openai-dotnet

Public

mirrored from https://github.com/openai/openai-dotnetAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
mailinhphan/clientOptions

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

codegen/generator/src/Visitors/ExperimentalAttributeVisitor.cs

175lines · modecode

1using System;
2using System.Collections.Generic;
3using System.Diagnostics.CodeAnalysis;
4using System.IO;
5using System.Linq;
6using System.Reflection;
7using Microsoft.TypeSpec.Generator.ClientModel;
8using Microsoft.TypeSpec.Generator.Primitives;
9using Microsoft.TypeSpec.Generator.Providers;
10using Microsoft.TypeSpec.Generator.Snippets;
11using Microsoft.TypeSpec.Generator.Statements;
12
13namespace OpenAILibraryPlugin.Visitors
14{
15 /// <summary>
16 /// A visitor to add the <see cref="ExperimentalAttribute"/> to types, properties, and methods that are not stable.
17 /// </summary>
18 public class ExperimentalAttributeVisitor : ScmLibraryVisitor
19 {
20 private const string _realtimeNamespace = "OpenAI.Realtime";
21 private static readonly AttributeStatement _experimental001Attribute = new(typeof(ExperimentalAttribute), Snippet.Literal("OPENAI001"));
22 private static readonly AttributeStatement _experimental002Attribute = new(typeof(ExperimentalAttribute), Snippet.Literal("OPENAI002"));
23 private static readonly AttributeStatement _experimentalCUA001Attribute = new(typeof(ExperimentalAttribute), Snippet.Literal("OPENAICUA001"));
24
25 // Stable sets loaded from the embedded ga-apis.yaml resource
26 private static readonly HashSet<string> _stableClasses;
27 private static readonly HashSet<string> _stableProperties;
28 private static readonly HashSet<string> _stableMethods;
29
30 static ExperimentalAttributeVisitor()
31 {
32 using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("ga-apis.yaml")
33 ?? throw new InvalidOperationException("Embedded resource 'ga-apis.yaml' not found.");
34 using StreamReader reader = new(stream);
35
36 _stableClasses = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
37 _stableProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
38 _stableMethods = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
39
40 HashSet<string>? current = null;
41 string? line;
42 while ((line = reader.ReadLine()) != null)
43 {
44 string trimmed = line.Trim();
45 if (trimmed.Length == 0 || trimmed.StartsWith("#"))
46 continue;
47
48 if (trimmed == "stableClasses:")
49 current = _stableClasses;
50 else if (trimmed == "stableProperties:")
51 current = _stableProperties;
52 else if (trimmed == "stableMethods:")
53 current = _stableMethods;
54 else if (trimmed.StartsWith("- ") && current != null)
55 current.Add(trimmed.Substring(2).Trim());
56 }
57 }
58
59 private static readonly HashSet<string> _OPENAICUA001AttributeTypes = new(StringComparer.OrdinalIgnoreCase)
60 {
61 "ComputerCallAction",
62 "ComputerCallActionKind",
63 "ComputerCallActionMouseButton",
64 "ComputerCallOutputResponseItem",
65 "ComputerCallOutputStatus",
66 "ComputerCallResponseItem",
67 "ComputerCallSafetyCheck",
68 "ComputerCallStatus",
69 "ComputerCallOutput",
70 "ComputerToolEnvironment",
71 };
72
73 protected override PropertyProvider? VisitProperty(PropertyProvider property)
74 {
75 // Skip properties that are already marked as experimental
76 if (property.Attributes.Any(attr => attr.Type.Equals(typeof(ExperimentalAttribute))))
77 {
78 return base.VisitProperty(property);
79 }
80
81 // Skip properties that are not public or are in non-stable classes
82 if ((!property.Modifiers.HasFlag(MethodSignatureModifiers.Public) &&
83 !property.Modifiers.HasFlag(MethodSignatureModifiers.Protected)) ||
84 !_stableClasses.Contains($"{property.EnclosingType.Type.Namespace}.{property.EnclosingType.Name}"))
85 {
86 return base.VisitProperty(property);
87 }
88
89 if (!_stableProperties.Contains($"{property.EnclosingType.Name}.{property.Name}"))
90 {
91 property.Update(
92 attributes: [.. property.Attributes,
93 property.EnclosingType.Type.Namespace.StartsWith(_realtimeNamespace) ? _experimental002Attribute : _experimental001Attribute]);
94
95 return property;
96 }
97
98 return base.VisitProperty(property);
99 }
100
101 protected override MethodProvider? VisitMethod(MethodProvider methodProvider)
102 {
103 // Skip methods that are not public or are in non-stable classes
104 if ((!methodProvider.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public) &&
105 !methodProvider.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Protected)) ||
106 !_stableClasses.Contains($"{methodProvider.EnclosingType.Type.Namespace}.{methodProvider.EnclosingType.Name}"))
107 {
108 return base.VisitMethod(methodProvider);
109 }
110
111 string lookupName = methodProvider.Signature.Parameters.Count switch
112 {
113 0 => $"{methodProvider.Signature.Name}",
114 1 => $"{methodProvider.Signature.Name}|{methodProvider.Signature.Parameters[0].Type.Name}",
115 _ => $"{methodProvider.Signature.Name}|{string.Join("|", methodProvider.Signature.Parameters.Select(p => p.Type.Name))}"
116 };
117
118 // Generate a lookup name based on method signature
119 string operatorPrefix = "operator ";
120 bool isOperator = methodProvider.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Operator);
121 bool isImplicit = methodProvider.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Implicit);
122 bool isExplicit = methodProvider.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Explicit);
123 lookupName = $"{methodProvider.EnclosingType.Name}.{(isOperator ? operatorPrefix : "")}{(isImplicit ? $"implicit {methodProvider.EnclosingType.Name}" : "")}{lookupName}";
124
125 if (!_stableMethods.Contains(lookupName))
126 {
127 methodProvider.Signature.Update(
128 attributes: [.. methodProvider.Signature.Attributes,
129 methodProvider.EnclosingType.Type.Namespace.StartsWith(_realtimeNamespace) || (methodProvider.Signature.ReturnType?.Namespace.StartsWith(_realtimeNamespace) ?? false) ?
130 _experimental002Attribute :
131 _experimental001Attribute]);
132
133 return methodProvider;
134 }
135
136 return base.VisitMethod(methodProvider);
137 }
138
139 // Tracks which (Namespace.Name) pairs have already been decorated in the
140 // current emit, so multiple TypeProviders that emit the same partial class
141 // (e.g., a model and its companion serialization partial) don't produce
142 // duplicate [Experimental] attributes.
143 private readonly HashSet<string> _attributedTypes = new(StringComparer.Ordinal);
144
145 protected override TypeProvider? VisitType(TypeProvider type)
146 {
147 // Decorate any public/protected generated type that isn't in the stable
148 // set. The provider-kind allow-list previously used here (ClientProvider,
149 // ModelProvider, ClientOptionsProvider, EnumProvider) silently skipped
150 // other generated public types -- e.g., ModelReaderWriterContext
151 // partials -- leaving them un-attributed.
152 // Visibility plus the stable-list check is sufficient to gate this.
153 if ((type.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public) ||
154 type.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Protected)) &&
155 !_stableClasses.Contains($"{type.Type.Namespace}.{type.Name}") &&
156 !type.Attributes.Any(attr => attr.Type.Equals(typeof(ExperimentalAttribute))) &&
157 _attributedTypes.Add($"{type.Type.Namespace}.{type.Name}"))
158 {
159 AttributeStatement experimentalAttribute = type.Type.Namespace switch
160 {
161 _ when type.Type.Namespace.StartsWith(_realtimeNamespace) => _experimental002Attribute,
162 _ when _OPENAICUA001AttributeTypes.Contains(type.Name) => _experimentalCUA001Attribute,
163 _ => _experimental001Attribute
164 };
165 type.Update(
166 attributes: [.. type.Attributes,
167 experimentalAttribute]);
168
169 return type;
170 }
171
172 return base.VisitType(type);
173 }
174 }
175}
176