Skip to content

Registration ​

services.AddTypeshift() registers every toolset and agent of your project and the projects it references. It returns a TypeshiftBuilder, whose With... calls register what your app supplies, such as the store and the chat client provider.

Project setup ​

bash
dotnet add package Typeshift --prerelease
RequirementWhy
.NET 10 and C# 14Generated members are extension blocks.
The package in every project that declares toolsets or agents, or calls AddTypeshift()The package carries the generator, and NuGet does not pass a generator on to the projects that reference yours (TSH019, TSH020).
GenerateDocumentationFileTools and the types they take get their descriptions from XML docs (TSH011).
Typeshift.Generated in InterceptorsNamespacesThe generator intercepts AddTypeshift() from that namespace. The package adds it for you.

XML docs of parameter types ​

The package brings Weboost.SchemaAnnotations, which turns the XML docs of your types into descriptions of the properties a tool's parameters hold. Typeshift adds a hidden class to every project that sets GenerateDocumentationFile. Weboost.SchemaAnnotations collects the project's XML docs in that class, and the project's registration module registers them when AddTypeshift() runs. A project of contracts with no toolsets or agents gets a module too, so the tools of the projects that reference it describe its types; it needs the package and GenerateDocumentationFile like any other. To leave these descriptions out, set:

xml
<TypeshiftSchemaAnnotations>false</TypeshiftSchemaAnnotations>

Delete your own [SchemaAnnotations] class

If your project already marks a class with [SchemaAnnotations], adding the Typeshift package gives it a second one. Weboost.SchemaAnnotations then generates no AIJsonSchemaCreateOptions.Annotated, and code that uses it stops compiling. Delete your own class, and the one the package adds brings Annotated back.

The id type ​

Settings ids and conversation ids share one type across the whole build. It is Guid unless TypeshiftIdType names another, set once in Directory.Build.props:

csproj
<PropertyGroup>
  <TypeshiftIdType>int</TypeshiftIdType>
</PropertyGroup>

snippet source | anchor

TypeshiftIdType takes a C# keyword (int, long, string), the name of a type in the System namespace, such as Guid, or any other type's full name with its namespace (TSH029). A public agent needs a public id type, since its host members take ids of it (TSH006).

Generated members take the id type, as in RunAsync(input, settingsId: 42). The generator also writes aliases that close ITypeshiftStore, ISettingsStore, IConversationStore, InMemoryTypeshiftStore, ConversationTracker and LiveRun over it, so you use these names without a type argument. The library never creates ids; your code chooses them. If a referenced project was built with another id type, the analyzer reports TSH021, and for a store of another id type it reports TSH028.

Providers have an id type of their own, TypeshiftProviderIdType, which is string unless set. See the provider id type.

What AddTypeshift() registers ​

A typical registration, with the chain:

cs
services.AddTypeshift()
    .WithStore<EfTypeshiftStore>()
    .WithChatClientProvider<KeyedChatClientProvider>()
    .WithRealtimeTracking()
    .WithRunMiddleware<BlockedTopics>()
    .WithToolMiddleware<AuditToolCalls>();

services.AddKeyedSingleton("openai", fast);
services.AddKeyedSingleton("anthropic", reasoning);

snippet source | anchor

fast and reasoning are IChatClients your app creates. The last two lines register them under the keys openai and anthropic, and KeyedChatClientProvider returns the client registered under a run's provider (the chat client provider).

AddTypeshift() itself registers these:

ServiceLifetime
Each toolsetSingleton
Each agentSingleton
AgentHost<TAgent> per agentTransient, so it runs with the services of the scope it was resolved from (Using an agent)
The JSON contexts of each assembly, which tools ask before the defaultsSingleton
ConversationTracker and the core services runs needSingleton

The generator writes a registration module for each project that declares toolsets or agents. AddTypeshift() runs the modules of the project that calls it and of every project it references that declares toolsets or agents. Each module registers once per service collection, so a second call to AddTypeshift() only returns another chain.

Because the generator intercepts the call, it works only in a project that references the package, and only when you call it on the service collection, such as services.AddTypeshift() or builder.Services.AddTypeshift(). A call the generator did not intercept throws an InvalidOperationException that says what to check.

A library's call registers the library's agents, not your app's

A library that calls AddTypeshift() in its own AddX() registers its own toolsets and agents and those of its references. Your app's own toolsets and agents need a call to AddTypeshift() in your app.

The chain ​

CallRegistersLifetimeNeeded by
WithStore<TStore>()TStore, and each of ISettingsStore, IConversationStore and ITypeshiftStore it implements, resolving to it. Call it once for each class.Singleton, so it takes no scoped services (Implementing a store)Settings ids, conversation ids, approval and handoffs. Without a store, every call that loads or saves throws, naming this method.
WithChatClientProvider<TProvider>()TProvider, and IChatClientProvider resolving to itScopedEvery run.
WithRealtimeTracking(RealtimeTrackingOptions? options = null)The options. By default, PublishInterval is 100 ms and MaxPendingUpdates 256.SingletonConversationTracker.WatchAsync.
WithApprovalRule<TRule>()One more IToolApprovalRuleScopedDeciding calls before your app is asked.
WithRunMiddleware<TMiddleware>()One more IRunMiddleware. The first registered is the outermost.ScopedWrapping every run.
WithToolMiddleware<TMiddleware>()One more IToolMiddleware. The first registered is the outermost.ScopedWrapping every tool call.
WithMcpToolProvider<TProvider>()IMcpToolProviderScopedAgents with UseMcpServer.

Scoped services resolve from the run's scope, so they can differ per request and use the current customer's credentials, folder or rules, for example.

Released under the MIT License.