Skip to content

Commit 3eb943b

Browse files
authored
Update example to SK 1.1, fix TS generator and marshaller bugs (#191)
1 parent 6168214 commit 3eb943b

10 files changed

Lines changed: 326 additions & 118 deletions

File tree

examples/semantic-kernel/example.js

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
// @ts-check
55

66
import dotnet from 'node-api-dotnet';
7+
import './bin/System.Text.Encodings.Web.js';
8+
import './bin/Microsoft.Extensions.DependencyInjection.js';
79
import './bin/Microsoft.Extensions.Logging.Abstractions.js';
810
import './bin/Microsoft.SemanticKernel.Abstractions.js';
911
import './bin/Microsoft.SemanticKernel.Core.js';
10-
import './bin/Microsoft.SemanticKernel.Connectors.AI.OpenAI.js';
11-
import './bin/Microsoft.SemanticKernel.TemplateEngine.Basic.js';
12+
import './bin/Microsoft.SemanticKernel.Connectors.OpenAI.js';
1213

1314
const Logging = dotnet.Microsoft.Extensions.Logging;
1415
const SK = dotnet.Microsoft.SemanticKernel;
@@ -28,18 +29,22 @@ const loggerFactory = {
2829
dispose() {}
2930
};
3031

31-
let kernelBuilder = new SK.KernelBuilder();
32-
kernelBuilder.WithLoggerFactory(loggerFactory);
32+
const kernelBuilder = SK.Kernel.CreateBuilder();
33+
//kernelBuilder.WithLoggerFactory(loggerFactory);
3334

3435
// The JS marshaller does not yet support extension methods.
35-
SK.OpenAIKernelBuilderExtensions.WithAzureOpenAIChatCompletionService(
36+
SK.OpenAIServiceCollectionExtensions.AddAzureOpenAIChatCompletion(
3637
kernelBuilder,
3738
process.env['OPENAI_DEPLOYMENT'] || '',
3839
process.env['OPENAI_ENDPOINT'] || '',
3940
process.env['OPENAI_KEY'] || '',
41+
// Include optional parameters to disambiguate the overload.
42+
undefined,
43+
undefined,
44+
undefined,
4045
);
4146

42-
const kernel = kernelBuilder.Build();
47+
const kernel = SK.KernelExtensions.Build(kernelBuilder);
4348

4449
const prompt = `{{$input}}
4550
@@ -57,15 +62,18 @@ such orders would conflict with the First Law.
5762
does not conflict with the First or Second Law.
5863
`;
5964

60-
const requestSettings = new SK.Connectors.AI.OpenAI.OpenAIRequestSettings();
61-
requestSettings.MaxTokens = 100;
65+
const executionSettings = new SK.Connectors.OpenAI.OpenAIPromptExecutionSettings();
66+
executionSettings.MaxTokens = 100;
6267

6368
// The JS marshaller does not yet support extension methods.
64-
const summaryFunction = SK.OpenAIKernelExtensions
65-
.CreateSemanticFunction(kernel, prompt, requestSettings);
69+
const summaryFunction = SK.KernelExtensions.CreateFunctionFromPrompt(
70+
kernel, prompt, executionSettings);
6671

67-
const summary = await SK.SKFunctionExtensions.InvokeAsync(
68-
summaryFunction, textToSummarize, kernel);
72+
const summarizeArguments = new Map();
73+
summarizeArguments.set('input', textToSummarize);
74+
75+
const summary = await kernel.InvokeAsync(
76+
summaryFunction, new SK.KernelArguments(summarizeArguments, undefined));
6977

7078
console.log();
7179
console.log(summary.toString());

examples/semantic-kernel/semantic-kernel.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="Microsoft.SemanticKernel" Version="1.0.0-beta8" />
11+
<PackageReference Include="Microsoft.SemanticKernel" Version="1.1.0" />
1212
<PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.4.*-*" />
1313
</ItemGroup>
1414

src/NodeApi.DotNetHost/JSMarshaller.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ public Expression<JSCallback> BuildFromJSConstructorExpression(ConstructorInfo c
351351

352352
for (int i = 0; i < parameters.Length; i++)
353353
{
354-
argVariables[i] = Expression.Variable(parameters[i].ParameterType, parameters[i].Name);
354+
argVariables[i] = Variable(parameters[i]);
355355
statements.Add(Expression.Assign(argVariables[i],
356356
BuildArgumentExpression(i, parameters[i])));
357357
}
@@ -1859,10 +1859,10 @@ private LambdaExpression BuildConvertFromJSValueExpression(Type toType)
18591859
// public type is passed to JS and then passed back to .NET as `object` type.
18601860

18611861
/*
1862-
* (T)(value.TryUnwrap() ?? value.TryGetValueExternal());
1862+
* (T)(value.TryUnwrap() ?? value.GetValueExternalOrPrimitive());
18631863
*/
1864-
MethodInfo getExternalMethod =
1865-
typeof(JSNativeApi).GetStaticMethod(nameof(JSNativeApi.TryGetValueExternal));
1864+
MethodInfo getExternalMethod = typeof(JSNativeApi).GetStaticMethod(
1865+
nameof(JSNativeApi.GetValueExternalOrPrimitive));
18661866
statements = new[]
18671867
{
18681868
Expression.Convert(

src/NodeApi.DotNetHost/ManagedHost.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,14 @@ JSValue removeListener(JSCallbackArgs args)
175175
}
176176

177177
public static bool IsTracingEnabled { get; } =
178+
Debugger.IsAttached ||
178179
Environment.GetEnvironmentVariable("TRACE_NODE_API_HOST") == "1";
179180

180181
public static void Trace(string msg)
181182
{
182183
if (IsTracingEnabled)
183184
{
185+
Debug.WriteLine(msg);
184186
Console.WriteLine(msg);
185187
Console.Out.Flush();
186188
}
@@ -213,7 +215,8 @@ public static napi_value InitializeModule(napi_env env, napi_value exports)
213215

214216
JSRuntime runtime = new NodejsRuntime();
215217

216-
if (Environment.GetEnvironmentVariable("TRACE_NODE_API_RUNTIME") != null)
218+
if (Debugger.IsAttached ||
219+
Environment.GetEnvironmentVariable("TRACE_NODE_API_RUNTIME") != null)
217220
{
218221
TraceSource trace = new(typeof(JSValue).Namespace!);
219222
trace.Switch.Level = SourceLevels.All;

src/NodeApi.DotNetHost/TypeExporter.cs

Lines changed: 56 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -94,62 +94,78 @@ private JSReference ExportType(Type type)
9494

9595
private JSReference ExportClass(Type type)
9696
{
97+
string typeName = type.Name;
98+
Trace($"### ExportClass({typeName}");
99+
97100
if (_exportedTypes.TryGetValue(type, out JSReference? classObjectReference))
98101
{
99102
return classObjectReference;
100103
}
101104

102105
Trace($"> {nameof(TypeExporter)}.ExportClass({type.FormatName()})");
103106

104-
bool isStatic = type.IsAbstract && type.IsSealed;
105-
Type classBuilderType =
106-
(type.IsValueType ? typeof(JSStructBuilder<>) : typeof(JSClassBuilder<>))
107-
.MakeGenericType(isStatic ? typeof(object) : type);
108-
109-
object classBuilder;
110-
if (type.IsInterface || isStatic || type.IsValueType)
111-
{
112-
classBuilder = classBuilderType.CreateInstance(
113-
new[] { typeof(string) }, new[] { type.Name });
114-
}
115-
else
107+
// Add a temporary null entry to the dictionary while exporting this type, in case the
108+
// type is encountered while exporting members. It will be non-null by the time this method returns
109+
// (or removed if an exception is thrown).
110+
_exportedTypes.Add(type, null!);
111+
try
116112
{
117-
ConstructorInfo[] constructors =
118-
type.GetConstructors(BindingFlags.Public | BindingFlags.Instance)
119-
.Where(IsSupportedConstructor)
120-
.ToArray();
121-
JSCallbackDescriptor constructorDescriptor;
122-
if (constructors.Length == 1 &&
123-
!constructors[0].GetParameters().Any((p) => p.IsOptional))
113+
bool isStatic = type.IsAbstract && type.IsSealed;
114+
Type classBuilderType =
115+
(type.IsValueType ? typeof(JSStructBuilder<>) : typeof(JSClassBuilder<>))
116+
.MakeGenericType(isStatic ? typeof(object) : type);
117+
118+
object classBuilder;
119+
if (type.IsInterface || isStatic || type.IsValueType)
124120
{
125-
constructorDescriptor =
126-
_marshaller.BuildFromJSConstructorExpression(constructors[0]).Compile();
121+
classBuilder = classBuilderType.CreateInstance(
122+
new[] { typeof(string) }, new[] { type.Name });
127123
}
128124
else
129125
{
130-
// Multiple constructors or optional parameters require overload resolution.
131-
constructorDescriptor =
132-
_marshaller.BuildConstructorOverloadDescriptor(constructors);
133-
}
126+
ConstructorInfo[] constructors =
127+
type.GetConstructors(BindingFlags.Public | BindingFlags.Instance)
128+
.Where(IsSupportedConstructor)
129+
.ToArray();
130+
JSCallbackDescriptor constructorDescriptor;
131+
if (constructors.Length == 1 &&
132+
!constructors[0].GetParameters().Any((p) => p.IsOptional))
133+
{
134+
constructorDescriptor =
135+
_marshaller.BuildFromJSConstructorExpression(constructors[0]).Compile();
136+
}
137+
else
138+
{
139+
// Multiple constructors or optional parameters require overload resolution.
140+
constructorDescriptor =
141+
_marshaller.BuildConstructorOverloadDescriptor(constructors);
142+
}
134143

135-
classBuilder = classBuilderType.CreateInstance(
136-
new[] { typeof(string), typeof(JSCallbackDescriptor) },
137-
new object[] { type.Name, constructorDescriptor });
138-
}
144+
classBuilder = classBuilderType.CreateInstance(
145+
new[] { typeof(string), typeof(JSCallbackDescriptor) },
146+
new object[] { type.Name, constructorDescriptor });
147+
}
139148

140-
ExportProperties(type, classBuilder);
141-
ExportMethods(type, classBuilder);
142-
ExportNestedTypes(type, classBuilder);
149+
ExportProperties(type, classBuilder);
150+
ExportMethods(type, classBuilder);
151+
ExportNestedTypes(type, classBuilder);
143152

144-
string defineMethodName = type.IsInterface ? "DefineInterface" :
145-
isStatic ? "DefineStaticClass" : type.IsValueType ? "DefineStruct" : "DefineClass";
146-
MethodInfo defineClassMethod = classBuilderType.GetInstanceMethod(defineMethodName);
147-
JSValue classObject = (JSValue)defineClassMethod.Invoke(
148-
classBuilder,
149-
defineClassMethod.GetParameters().Select((_) => (object?)null).ToArray())!;
153+
string defineMethodName = type.IsInterface ? "DefineInterface" :
154+
isStatic ? "DefineStaticClass" : type.IsValueType ? "DefineStruct" : "DefineClass";
155+
MethodInfo defineClassMethod = classBuilderType.GetInstanceMethod(defineMethodName);
156+
JSValue classObject = (JSValue)defineClassMethod.Invoke(
157+
classBuilder,
158+
defineClassMethod.GetParameters().Select((_) => (object?)null).ToArray())!;
150159

151-
classObjectReference = new JSReference(classObject);
152-
_exportedTypes.Add(type, classObjectReference);
160+
classObjectReference = new JSReference(classObject);
161+
_exportedTypes[type] = classObjectReference;
162+
}
163+
catch
164+
{
165+
// Clean up the temporary null entry.
166+
_exportedTypes.Remove(type);
167+
throw;
168+
}
153169

154170
// Also export any types returned by properties or methods of this type, because
155171
// they might otherwise not be referenced by JS before they are used.

src/NodeApi.Generator/SourceGenerator.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public abstract class SourceGenerator
2424

2525
private static readonly Regex s_paragraphBreakRegex = new(@" *\<para */\> *");
2626

27+
protected const char NonBreakingSpace = (char)0xA0;
28+
2729
public enum DiagnosticId
2830
{
2931
NoExports = 1000,
@@ -193,11 +195,11 @@ protected static IEnumerable<string> WrapComment(string comment, int wrapColumn)
193195
}
194196
}
195197

196-
yield return comment.Substring(0, i).TrimEnd();
198+
yield return comment.Substring(0, i).TrimEnd().Replace(NonBreakingSpace, ' ');
197199
comment = comment.Substring(i + 1);
198200
}
199201

200-
yield return comment.TrimEnd();
202+
yield return comment.TrimEnd().Replace(NonBreakingSpace, ' ');
201203
}
202204
}
203205
}

0 commit comments

Comments
 (0)