Rebuilt how AI Studio knows what a model can do (#960)
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-09-13 14:17:25 +02:00
1 parent d21e09dd1e
commit d85b4e71b6
287 files changed
+18341 -3677

No files matched your search

@@ -0,0 +1,41 @@
using static AIStudio.Provider.Capability;
namespace AIStudio.Models.OpenAI;
/// <summary>
/// GPT-3.5, which answers with text and does nothing else.
/// </summary>
public sealed class Gpt35Family : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://platform.openai.com/docs/models", new DateOnly(2026, 9, 11), "Ported unchanged from the rules in ProviderExtensions.OpenAI.cs: text in, text out, no tools and no images.");
/// <inheritdoc />
public override IReadOnlyList<ModelSource> FurtherSources =>
[
new("https://github.com/openai/tiktoken/blob/main/tiktoken/model.py", new DateOnly(2026, 9, 12), "OpenAI's own mapping from model names to encodings. It maps \"gpt-3.5\", \"gpt-3.5-turbo\" and the prefix \"gpt-3.5-turbo-\" to cl100k_base.")
];
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder)
{
builder.Rule("gpt-3.5").AsPrefix()
.Capabilities(TEXT_INPUT | TEXT_OUTPUT)
.Apis(CHAT_COMPLETION_API)
.Tokenizer(TokenizerKind.TIKTOKEN, "cl100k_base");
//
// The odd one out, and kept odd on purpose: the previous rules put this one model on the
// Responses API and every other GPT-3.5 on the chat completion API. It reads like an
// oversight, but what the app answers today is what the snapshot pins, and correcting it is
// a decision of its own rather than something to slip into a port.
//
builder.Rule("gpt-3.5-turbo").AsExact()
.Capabilities(TEXT_INPUT | TEXT_OUTPUT)
.Apis(RESPONSES_API)
.Tokenizer(TokenizerKind.TIKTOKEN, "cl100k_base");
}
}
@@ -0,0 +1,40 @@
using static AIStudio.Provider.Capability;
namespace AIStudio.Models.OpenAI;
/// <summary>
/// GPT-4 and GPT-4 Turbo.
/// </summary>
/// <remarks>
/// GPT-4o is not one of these, which the name hides and the matching does not: a rule bound to the
/// start of a name only answers where a name part ends, and in "gpt-4o" the part goes on. The
/// previous rules had to say that twice, once as an exact comparison and once as a prefix.
/// </remarks>
public sealed class Gpt4Family : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://developers.openai.com/api/docs/models/gpt-4-turbo", new DateOnly(2026, 9, 12), "Capabilities ported unchanged from ProviderExtensions.OpenAI.cs: GPT-4 is text only, Turbo adds images and tool calling. The windows are the documented 8,192 tokens of GPT-4 and the 128,000 Turbo raised it to.");
/// <inheritdoc />
public override IReadOnlyList<ModelSource> FurtherSources =>
[
new("https://github.com/openai/tiktoken/blob/main/tiktoken/model.py", new DateOnly(2026, 9, 12), "OpenAI's own mapping from model names to encodings. It maps \"gpt-4\" and the prefix \"gpt-4-\" to cl100k_base, so Turbo uses it too -- the newer o200k_base begins with the 4o line, which is a family of its own here.")
];
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder)
{
builder.Rule("gpt-4").AsPrefix()
.Capabilities(TEXT_INPUT | TEXT_OUTPUT)
.Apis(RESPONSES_API)
.ContextWindow(8_192)
.Tokenizer(TokenizerKind.TIKTOKEN, "cl100k_base");
builder.Rule("gpt-4-turbo").AsPrefix().Inherits()
.Capabilities(MULTIPLE_IMAGE_INPUT | FUNCTION_CALLING)
.ContextWindow(128_000);
}
}
@@ -0,0 +1,56 @@
using static AIStudio.Provider.Capability;
// ReSharper disable InconsistentNaming
namespace AIStudio.Models.OpenAI;
/// <summary>
/// GPT-4o, including its mini and its audio preview.
/// </summary>
/// <remarks>
/// The previous rules never named this family. Its models reached the last line of the OpenAI
/// function, the one that answers for everything nobody wrote a rule for, and that line happened
/// to describe GPT-4o exactly. Writing it down changes no answer and takes the family out of the
/// fallback, where a wrong answer looks like no answer.
/// </remarks>
public sealed class Gpt4oFamily : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://developers.openai.com/api/docs/models/gpt-4o", new DateOnly(2026, 9, 12), "The answer the previous rules gave these models through their fallback: images, tool calling, and web search on the Responses API. The model page states a 128,000 token window, which the minis and the search previews share.");
/// <inheritdoc />
public override IReadOnlyList<ModelSource> FurtherSources =>
[
new("https://github.com/openai/tiktoken/blob/main/tiktoken/model.py", new DateOnly(2026, 9, 12), "OpenAI's own mapping from model names to encodings. It maps the prefix \"gpt-4o-\" to o200k_base, which covers the minis and the search previews as well.")
];
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder)
{
builder.Rule("gpt-4o").AsPrefix()
.Capabilities(TEXT_INPUT | MULTIPLE_IMAGE_INPUT | TEXT_OUTPUT | FUNCTION_CALLING | WEB_SEARCH)
.Apis(RESPONSES_API)
.ContextWindow(128_000)
.Tokenizer(TokenizerKind.TIKTOKEN, "o200k_base");
//
// The search previews are the same generation and almost nothing like it: they search the
// web and do nothing else, no images and no tools, and they answer only through the chat
// completion API. Stated in full rather than inherited, because there is barely anything of
// the family left in them.
//
builder.Rule("gpt-4o-search-preview").AsExact()
.Capabilities(TEXT_INPUT | TEXT_OUTPUT | WEB_SEARCH)
.Apis(CHAT_COMPLETION_API)
.ContextWindow(128_000)
.Tokenizer(TokenizerKind.TIKTOKEN, "o200k_base");
builder.Rule("gpt-4o-mini-search-preview").AsExact()
.Capabilities(TEXT_INPUT | TEXT_OUTPUT | WEB_SEARCH)
.Apis(CHAT_COMPLETION_API)
.ContextWindow(128_000)
.Tokenizer(TokenizerKind.TIKTOKEN, "o200k_base");
}
}
@@ -0,0 +1,80 @@
using static AIStudio.Provider.Capability;
namespace AIStudio.Models.OpenAI;
/// <summary>
/// The whole GPT-5 line, from GPT-5 to GPT-5.6.
/// </summary>
/// <remarks>
/// One family rather than six, because the generations differ in one sentence each and stating that
/// sentence is the entire content: GPT-5 reasons always and answers only through the Responses API,
/// GPT-5.1 reasons on request and answers through both, GPT-5.5 reasons unless told not to.
///
/// The dot is what keeps the generations apart. A rule bound to the start of a name ends at a name
/// part, and a dot does not end one, so "gpt-5" does not answer for "gpt-5.1" -- which is exactly
/// what the previous rules spelled out one comparison at a time.
///
/// None of these models writes images itself. They can ask for one through the image generation
/// tool, which is a tool call producing a picture from a separate model, and reporting that as an
/// output modality would have the chat offer to receive images which never arrive.
/// </remarks>
public sealed class Gpt5Family : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://developers.openai.com/api/docs/models", new DateOnly(2026, 9, 12), "Capabilities ported unchanged from ProviderExtensions.OpenAI.cs, one rule per generation, except that the chat alias no longer inherits the reasoning it is named for not having. Context windows read per generation from the model pages below that URL.");
/// <inheritdoc />
public override IReadOnlyList<ModelSource> FurtherSources =>
[
new("https://github.com/openai/tiktoken/blob/main/tiktoken/model.py", new DateOnly(2026, 9, 12), "OpenAI's own mapping from model names to encodings. It maps the prefix \"gpt-5\" to o200k_base, which covers every model of this line.")
];
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder)
{
//
// The window grows once in this line, between 5.3 and 5.4: everything up to 5.2 is
// documented at 400,000 tokens and everything from 5.4 on at 1,050,000. Both numbers are
// the whole window, input and output together, which is how OpenAI states them.
//
builder.Rule("gpt-5").AsPrefix()
.Capabilities(TEXT_INPUT | MULTIPLE_IMAGE_INPUT | TEXT_OUTPUT | FUNCTION_CALLING | WEB_SEARCH)
.Apis(RESPONSES_API)
.Reasoning(ReasoningSupport.ALWAYS)
.ContextWindow(400_000)
.Tokenizer(TokenizerKind.TIKTOKEN, "o200k_base");
//
// The alias for the model of this generation which does not reason. The previous rules had
// it swallowed by the prefix above and told it that it always reasons, which is the one
// thing its name rules out. Here the longer pattern simply wins.
//
builder.Rule("gpt-5-chat").AsPrefix().Inherits()
.Reasoning(ReasoningSupport.NONE);
builder.Rule("gpt-5.1").AsPrefix().InheritsFrom("gpt-5")
.Apis(CHAT_COMPLETION_API)
.Reasoning(ReasoningSupport.OPTIONAL);
builder.Rule("gpt-5.2").AsPrefix().InheritsFrom("gpt-5.1");
//
// The one generation OpenAI documents nothing about: there is no model page for it, so the
// rule exists to keep a 5.3 answering like the rest of the line if one ever appears. What it
// must not do is carry 5.1's window as if somebody had looked it up.
//
builder.Rule("gpt-5.3").AsPrefix().InheritsFrom("gpt-5.1")
.WithoutContextWindow();
builder.Rule("gpt-5.4").AsPrefix().InheritsFrom("gpt-5.1")
.ContextWindow(1_050_000);
builder.Rule("gpt-5.5").AsPrefix().InheritsFrom("gpt-5.4")
.Reasoning(ReasoningSupport.ON_BY_DEFAULT);
builder.Rule("gpt-5.6").AsPrefix().InheritsFrom("gpt-5.5");
}
}
@@ -0,0 +1,27 @@
using static AIStudio.Provider.Capability;
namespace AIStudio.Models.OpenAI;
/// <summary>
/// GPT-6 Astra.
/// </summary>
/// <remarks>
/// Unlike the 5.5 and 5.6 models it reasons on every request: the effort reaches from low to max,
/// and there is no setting which switches thinking off.
/// </remarks>
public sealed class Gpt6AstraFamily : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://developers.openai.com/api/docs/models", new DateOnly(2026, 9, 12), "Capabilities ported unchanged from ProviderExtensions.OpenAI.cs: reasons on every request, both APIs. The models page states the window as 1.05M tokens.");
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder) =>
builder.Rule("gpt-6-astra").AsPrefix()
.Capabilities(TEXT_INPUT | MULTIPLE_IMAGE_INPUT | TEXT_OUTPUT | FUNCTION_CALLING | WEB_SEARCH)
.Apis(RESPONSES_API | CHAT_COMPLETION_API)
.Reasoning(ReasoningSupport.ALWAYS)
.ContextWindow(1_050_000);
}
@@ -0,0 +1,31 @@
using static AIStudio.Provider.Capability;
namespace AIStudio.Models.OpenAI;
/// <summary>
/// gpt-oss, the weights OpenAI published.
/// </summary>
/// <remarks>
/// The only OpenAI model anybody else may serve, and the reason the rest of this folder does not
/// have to worry about being confused with it: "gpt-oss" is a name part of its own, while every
/// cloud model of theirs carries a version behind the "gpt". The previous rules needed a function
/// to tell the two apart, and it is the specificity which does it here.
///
/// It browses through the harmony format it was trained on, which is why web search is stated even
/// though nothing else among the open weights has it.
/// </remarks>
public sealed class GptOssFamily : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://huggingface.co/openai/gpt-oss-120b", new DateOnly(2026, 9, 12), "Capabilities ported unchanged from the gpt-oss check of ProviderExtensions.OpenSource.cs. The model card states a 128k token window, which both sizes share.");
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder) =>
builder.Rule("gpt-oss").AsSegment()
.Capabilities(TEXT_INPUT | TEXT_OUTPUT | FUNCTION_CALLING | WEB_SEARCH)
.Apis(CHAT_COMPLETION_API)
.ContextWindow(131_072);
}
@@ -0,0 +1,63 @@
using static AIStudio.Provider.Capability;
namespace AIStudio.Models.OpenAI;
/// <summary>
/// The o-series: o1, o3, o4 and their minis, the models which reason before they answer.
/// </summary>
/// <remarks>
/// Every one of them always reasons; what differs is how much else they can do, and the minis are
/// consistently the ones which can do less. That the mini is not simply a smaller version of its
/// generation is why each of them is stated in full: o1-mini has neither images nor tools and
/// answers only through the chat completion API, while o3-mini has tools but no images.
///
/// The minis need no ordering: their patterns are longer, so they win over the generation they
/// belong to without anybody saying which rule to try first.
/// </remarks>
public sealed class OSeriesFamily : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://developers.openai.com/api/docs/models", new DateOnly(2026, 9, 12), "Capabilities ported unchanged from ProviderExtensions.OpenAI.cs, one rule per generation and one per mini. The o1 and o3 pages state 200,000 tokens; the two cut-down minis have no page of their own, so no window is stated for them.");
/// <inheritdoc />
public override IReadOnlyList<ModelSource> FurtherSources =>
[
new("https://github.com/openai/tiktoken/blob/main/tiktoken/model.py", new DateOnly(2026, 9, 12), "OpenAI's own mapping from model names to encodings. It maps \"o1\", \"o3\", \"o4-mini\" and the prefixes \"o1-\", \"o3-\" and \"o4-mini-\" to o200k_base, so the whole series shares one encoding.")
];
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder)
{
builder.Rule("o1").AsPrefix()
.Capabilities(TEXT_INPUT | MULTIPLE_IMAGE_INPUT | TEXT_OUTPUT | FUNCTION_CALLING)
.Apis(RESPONSES_API)
.Reasoning(ReasoningSupport.ALWAYS)
.ContextWindow(200_000)
.Tokenizer(TokenizerKind.TIKTOKEN, "o200k_base");
builder.Rule("o1-mini").AsPrefix()
.Capabilities(TEXT_INPUT | TEXT_OUTPUT)
.Apis(CHAT_COMPLETION_API)
.Reasoning(ReasoningSupport.ALWAYS)
.Tokenizer(TokenizerKind.TIKTOKEN, "o200k_base");
builder.Rule("o3").AsPrefix()
.Capabilities(TEXT_INPUT | MULTIPLE_IMAGE_INPUT | TEXT_OUTPUT | FUNCTION_CALLING | WEB_SEARCH)
.Apis(RESPONSES_API)
.Reasoning(ReasoningSupport.ALWAYS)
.ContextWindow(200_000)
.Tokenizer(TokenizerKind.TIKTOKEN, "o200k_base");
builder.Rule("o3-mini").AsPrefix()
.Capabilities(TEXT_INPUT | TEXT_OUTPUT | FUNCTION_CALLING)
.Apis(RESPONSES_API)
.Reasoning(ReasoningSupport.ALWAYS)
.Tokenizer(TokenizerKind.TIKTOKEN, "o200k_base");
// The one mini which is not cut down: it is the o3 generation under another number.
builder.Rule("o4-mini").AsPrefix().InheritsFrom("o3");
}
}
@@ -0,0 +1,42 @@
using AIStudio.Provider;
using static AIStudio.Provider.Capability;
namespace AIStudio.Models.OpenAI;
/// <summary>
/// OpenAI's embedding models, which turn text into a vector and answer nothing.
/// </summary>
/// <remarks>
/// The previous rules had no idea these existed. They fell through to the OpenAI fallback and were
/// told they see images, call functions, and search the web -- an answer with nothing right about
/// it, for models the app asks for through a separate method of its own.
///
/// The generation is named rather than the prefix "text-embedding": Google and Alibaba Cloud name
/// their own embedding models the same way, and those are their models, not these.
/// </remarks>
public sealed class OpenAIEmbeddingFamily : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://platform.openai.com/docs/guides/embeddings", new DateOnly(2026, 9, 11), "The app lists these under IProvider.GetEmbeddingModels, which is where the statement that they embed comes from.");
/// <inheritdoc />
public override IReadOnlyList<ModelSource> FurtherSources =>
[
new("https://github.com/openai/tiktoken/blob/main/tiktoken/model.py", new DateOnly(2026, 9, 12), "OpenAI's own mapping from model names to encodings. It names all three of these models -- text-embedding-3-small, text-embedding-3-large and text-embedding-ada-002 -- and maps every one of them to cl100k_base rather than to the newer o200k_base of the chat models.")
];
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder)
{
builder.Rule("text-embedding-3").AsPrefix()
.Capabilities(TEXT_INPUT | EMBEDDING)
.Kind(ModelKind.EMBEDDING)
.Tokenizer(TokenizerKind.TIKTOKEN, "cl100k_base");
builder.Rule("text-embedding-ada").AsPrefix().Inherits();
}
}
@@ -0,0 +1,29 @@
using AIStudio.Provider;
using static AIStudio.Provider.Capability;
namespace AIStudio.Models.OpenAI;
/// <summary>
/// Whisper, which listens and writes down what it heard.
/// </summary>
/// <remarks>
/// OpenAI built it and released the weights, so it turns up far beyond OpenAI's own API: Fireworks,
/// the GWDG, and Groq all serve a Whisper. This family is bound to no provider for that reason --
/// it is the same model wherever it runs, and the previous rules answered for it at every one of
/// those places with the global fallback, tool calling included.
/// </remarks>
public sealed class WhisperFamily : ModelFamily
{
/// <inheritdoc />
public override ModelVendor Vendor => ModelVendor.OPEN_AI;
/// <inheritdoc />
public override ModelSource Source => new("https://platform.openai.com/docs/guides/speech-to-text", new DateOnly(2026, 9, 11), "The app lists these under IProvider.GetTranscriptionModels, which is where the statement that they transcribe comes from.");
/// <inheritdoc />
protected override void Declare(ModelFamilyBuilder builder) =>
builder.Rule("whisper").AsSegment()
.Capabilities(SPEECH_INPUT | TEXT_OUTPUT)
.Kind(ModelKind.TRANSCRIPTION);
}