diff --git a/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/.aspire/settings.json b/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/.aspire/settings.json index 988a3a98f5c..50026fd6e5e 100644 --- a/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/.aspire/settings.json +++ b/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/.aspire/settings.json @@ -4,4 +4,4 @@ "packages": { "Aspire.Hosting.GitHub.Models": "" } -} +} \ No newline at end of file diff --git a/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/.modules/.codegen-hash b/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/.modules/.codegen-hash index aa15d230618..aefe809b9a4 100644 --- a/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/.modules/.codegen-hash +++ b/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/.modules/.codegen-hash @@ -1 +1 @@ -1E14FFBC022E165AF28594D89038A62157C344105538488ECA98C041C3422D03 \ No newline at end of file +C13880F095DBA862665782C34D611AD1BB6B65C79C0D482A6EB5C7CACE3CDB53 \ No newline at end of file diff --git a/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/.modules/aspire.ts b/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/.modules/aspire.ts index 58e335d3aff..acbba8e2442 100644 --- a/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/.modules/aspire.ts +++ b/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/.modules/aspire.ts @@ -152,6 +152,53 @@ export enum EndpointProperty { HostAndPort = "HostAndPort", } +/** Enum type for GitHubModelName */ +export enum GitHubModelName { + AI21Jamba15Large = "AI21Jamba15Large", + CohereCommandA = "CohereCommandA", + CohereCommandR082024 = "CohereCommandR082024", + CohereCommandRPlus082024 = "CohereCommandRPlus082024", + DeepSeekR1 = "DeepSeekR1", + DeepSeekR10528 = "DeepSeekR10528", + DeepSeekV30324 = "DeepSeekV30324", + Llama4Maverick17B128EInstructFP8 = "Llama4Maverick17B128EInstructFP8", + Llama4Scout17B16EInstruct = "Llama4Scout17B16EInstruct", + Llama3211BVisionInstruct = "Llama3211BVisionInstruct", + Llama3290BVisionInstruct = "Llama3290BVisionInstruct", + Llama3370BInstruct = "Llama3370BInstruct", + MetaLlama31405BInstruct = "MetaLlama31405BInstruct", + MetaLlama318BInstruct = "MetaLlama318BInstruct", + MaiDSR1 = "MaiDSR1", + Phi4 = "Phi4", + Phi4MiniInstruct = "Phi4MiniInstruct", + Phi4MiniReasoning = "Phi4MiniReasoning", + Phi4MultimodalInstruct = "Phi4MultimodalInstruct", + Phi4Reasoning = "Phi4Reasoning", + Codestral2501 = "Codestral2501", + Ministral3B = "Ministral3B", + MistralMedium32505 = "MistralMedium32505", + MistralSmall31 = "MistralSmall31", + OpenAIGpt41 = "OpenAIGpt41", + OpenAIGpt41Mini = "OpenAIGpt41Mini", + OpenAIGpt41Nano = "OpenAIGpt41Nano", + OpenAIGpt4o = "OpenAIGpt4o", + OpenAIGpt4oMini = "OpenAIGpt4oMini", + OpenAIGpt5 = "OpenAIGpt5", + OpenAIGpt5ChatPreview = "OpenAIGpt5ChatPreview", + OpenAIGpt5Mini = "OpenAIGpt5Mini", + OpenAIGpt5Nano = "OpenAIGpt5Nano", + OpenAIO1 = "OpenAIO1", + OpenAIO1Mini = "OpenAIO1Mini", + OpenAIO1Preview = "OpenAIO1Preview", + OpenAIO3 = "OpenAIO3", + OpenAIO3Mini = "OpenAIO3Mini", + OpenAIO4Mini = "OpenAIO4Mini", + OpenAITextEmbedding3Large = "OpenAITextEmbedding3Large", + OpenAITextEmbedding3Small = "OpenAITextEmbedding3Small", + Grok3 = "Grok3", + Grok3Mini = "Grok3Mini", +} + /** Enum type for IconVariant */ export enum IconVariant { Regular = "Regular", @@ -261,6 +308,10 @@ export interface AddConnectionStringOptions { environmentVariableName?: string; } +export interface AddGitHubModelByIdOptions { + organization?: ParameterResource; +} + export interface AddGitHubModelOptions { organization?: ParameterResource; } @@ -1005,23 +1056,40 @@ export class DistributedApplicationBuilder { return new ProjectResourcePromise(this._addProjectInternal(name, projectPath, launchProfileName)); } - /** Exports AddGitHubModel for polyglot app hosts. */ + /** Adds a GitHub Model resource to the distributed application model. */ /** @internal */ - async _addGitHubModelInternal(name: string, model: string, organization?: ParameterResource): Promise { + async _addGitHubModelInternal(name: string, model: GitHubModelName, organization?: ParameterResource): Promise { const rpcArgs: Record = { builder: this._handle, name, model }; if (organization !== undefined) rpcArgs.organization = organization; const result = await this._client.invokeCapability( - 'Aspire.Hosting.GitHub.Models/addGitHubModel1', + 'Aspire.Hosting.GitHub.Models/addGitHubModel', rpcArgs ); return new GitHubModelResource(result, this._client); } - addGitHubModel(name: string, model: string, options?: AddGitHubModelOptions): GitHubModelResourcePromise { + addGitHubModel(name: string, model: GitHubModelName, options?: AddGitHubModelOptions): GitHubModelResourcePromise { const organization = options?.organization; return new GitHubModelResourcePromise(this._addGitHubModelInternal(name, model, organization)); } + /** Adds a GitHub Model resource using a model identifier string. */ + /** @internal */ + async _addGitHubModelByIdInternal(name: string, modelId: string, organization?: ParameterResource): Promise { + const rpcArgs: Record = { builder: this._handle, name, modelId }; + if (organization !== undefined) rpcArgs.organization = organization; + const result = await this._client.invokeCapability( + 'Aspire.Hosting.GitHub.Models/addGitHubModelById', + rpcArgs + ); + return new GitHubModelResource(result, this._client); + } + + addGitHubModelById(name: string, modelId: string, options?: AddGitHubModelByIdOptions): GitHubModelResourcePromise { + const organization = options?.organization; + return new GitHubModelResourcePromise(this._addGitHubModelByIdInternal(name, modelId, organization)); + } + } /** @@ -1067,11 +1135,16 @@ export class DistributedApplicationBuilderPromise implements PromiseLike obj.addProject(name, projectPath, launchProfileName))); } - /** Exports AddGitHubModel for polyglot app hosts. */ - addGitHubModel(name: string, model: string, options?: AddGitHubModelOptions): GitHubModelResourcePromise { + /** Adds a GitHub Model resource to the distributed application model. */ + addGitHubModel(name: string, model: GitHubModelName, options?: AddGitHubModelOptions): GitHubModelResourcePromise { return new GitHubModelResourcePromise(this._promise.then(obj => obj.addGitHubModel(name, model, options))); } + /** Adds a GitHub Model resource using a model identifier string. */ + addGitHubModelById(name: string, modelId: string, options?: AddGitHubModelByIdOptions): GitHubModelResourcePromise { + return new GitHubModelResourcePromise(this._promise.then(obj => obj.addGitHubModelById(name, modelId, options))); + } + } // ============================================================================ @@ -1711,6 +1784,21 @@ export class ContainerResource extends ResourceBuilderBase { + const rpcArgs: Record = { builder: this._handle, key }; + const result = await this._client.invokeCapability( + 'Aspire.Hosting/withHealthCheck', + rpcArgs + ); + return new ContainerResource(result, this._client); + } + + /** Adds a health check by key */ + withHealthCheck(key: string): ContainerResourcePromise { + return new ContainerResourcePromise(this._withHealthCheckInternal(key)); + } + /** @internal */ private async _withHttpHealthCheckInternal(path?: string, statusCode?: number, endpointName?: string): Promise { const rpcArgs: Record = { builder: this._handle }; @@ -1979,6 +2067,11 @@ export class ContainerResourcePromise implements PromiseLike return new ContainerResourcePromise(this._promise.then(obj => obj.waitForCompletion(dependency, options))); } + /** Adds a health check by key */ + withHealthCheck(key: string): ContainerResourcePromise { + return new ContainerResourcePromise(this._promise.then(obj => obj.withHealthCheck(key))); + } + /** Adds an HTTP health check */ withHttpHealthCheck(options?: WithHttpHealthCheckOptions): ContainerResourcePromise { return new ContainerResourcePromise(this._promise.then(obj => obj.withHttpHealthCheck(options))); @@ -2484,6 +2577,21 @@ export class ExecutableResource extends ResourceBuilderBase { + const rpcArgs: Record = { builder: this._handle, key }; + const result = await this._client.invokeCapability( + 'Aspire.Hosting/withHealthCheck', + rpcArgs + ); + return new ExecutableResource(result, this._client); + } + + /** Adds a health check by key */ + withHealthCheck(key: string): ExecutableResourcePromise { + return new ExecutableResourcePromise(this._withHealthCheckInternal(key)); + } + /** @internal */ private async _withHttpHealthCheckInternal(path?: string, statusCode?: number, endpointName?: string): Promise { const rpcArgs: Record = { builder: this._handle }; @@ -2698,6 +2806,11 @@ export class ExecutableResourcePromise implements PromiseLike obj.waitForCompletion(dependency, options))); } + /** Adds a health check by key */ + withHealthCheck(key: string): ExecutableResourcePromise { + return new ExecutableResourcePromise(this._promise.then(obj => obj.withHealthCheck(key))); + } + /** Adds an HTTP health check */ withHttpHealthCheck(options?: WithHttpHealthCheckOptions): ExecutableResourcePromise { return new ExecutableResourcePromise(this._promise.then(obj => obj.withHttpHealthCheck(options))); @@ -2837,6 +2950,21 @@ export class GitHubModelResource extends ResourceBuilderBase { + const rpcArgs: Record = { builder: this._handle, key }; + const result = await this._client.invokeCapability( + 'Aspire.Hosting/withHealthCheck', + rpcArgs + ); + return new GitHubModelResource(result, this._client); + } + + /** Adds a health check by key */ + withHealthCheck(key: string): GitHubModelResourcePromise { + return new GitHubModelResourcePromise(this._withHealthCheckInternal(key)); + } + /** @internal */ private async _withCommandInternal(name: string, displayName: string, executeCommand: (arg: ExecuteCommandContext) => Promise, commandOptions?: CommandOptions): Promise { const executeCommandId = registerCallback(async (argData: unknown) => { @@ -2893,11 +3021,26 @@ export class GitHubModelResource extends ResourceBuilderBase { + const rpcArgs: Record = { builder: this._handle }; + const result = await this._client.invokeCapability( + 'Aspire.Hosting.GitHub.Models/enableHealthCheck', + rpcArgs + ); + return new GitHubModelResource(result, this._client); + } + + /** Adds a health check for the GitHub Model resource. */ + enableHealthCheck(): GitHubModelResourcePromise { + return new GitHubModelResourcePromise(this._enableHealthCheckInternal()); + } + } /** @@ -2945,6 +3088,11 @@ export class GitHubModelResourcePromise implements PromiseLike obj.withExplicitStart())); } + /** Adds a health check by key */ + withHealthCheck(key: string): GitHubModelResourcePromise { + return new GitHubModelResourcePromise(this._promise.then(obj => obj.withHealthCheck(key))); + } + /** Adds a resource command */ withCommand(name: string, displayName: string, executeCommand: (arg: ExecuteCommandContext) => Promise, options?: WithCommandOptions): GitHubModelResourcePromise { return new GitHubModelResourcePromise(this._promise.then(obj => obj.withCommand(name, displayName, executeCommand, options))); @@ -2960,11 +3108,16 @@ export class GitHubModelResourcePromise implements PromiseLike obj.getResourceName()); } - /** Exports WithApiKey for polyglot app hosts. */ + /** Configures the API key for the GitHub Model resource. */ withApiKey(apiKey: ParameterResource): GitHubModelResourcePromise { return new GitHubModelResourcePromise(this._promise.then(obj => obj.withApiKey(apiKey))); } + /** Adds a health check for the GitHub Model resource. */ + enableHealthCheck(): GitHubModelResourcePromise { + return new GitHubModelResourcePromise(this._promise.then(obj => obj.enableHealthCheck())); + } + } // ============================================================================ @@ -3101,6 +3254,21 @@ export class ParameterResource extends ResourceBuilderBase { + const rpcArgs: Record = { builder: this._handle, key }; + const result = await this._client.invokeCapability( + 'Aspire.Hosting/withHealthCheck', + rpcArgs + ); + return new ParameterResource(result, this._client); + } + + /** Adds a health check by key */ + withHealthCheck(key: string): ParameterResourcePromise { + return new ParameterResourcePromise(this._withHealthCheckInternal(key)); + } + /** @internal */ private async _withCommandInternal(name: string, displayName: string, executeCommand: (arg: ExecuteCommandContext) => Promise, commandOptions?: CommandOptions): Promise { const executeCommandId = registerCallback(async (argData: unknown) => { @@ -3199,6 +3367,11 @@ export class ParameterResourcePromise implements PromiseLike return new ParameterResourcePromise(this._promise.then(obj => obj.withExplicitStart())); } + /** Adds a health check by key */ + withHealthCheck(key: string): ParameterResourcePromise { + return new ParameterResourcePromise(this._promise.then(obj => obj.withHealthCheck(key))); + } + /** Adds a resource command */ withCommand(name: string, displayName: string, executeCommand: (arg: ExecuteCommandContext) => Promise, options?: WithCommandOptions): ParameterResourcePromise { return new ParameterResourcePromise(this._promise.then(obj => obj.withCommand(name, displayName, executeCommand, options))); @@ -3679,6 +3852,21 @@ export class ProjectResource extends ResourceBuilderBase return new ProjectResourcePromise(this._waitForCompletionInternal(dependency, exitCode)); } + /** @internal */ + private async _withHealthCheckInternal(key: string): Promise { + const rpcArgs: Record = { builder: this._handle, key }; + const result = await this._client.invokeCapability( + 'Aspire.Hosting/withHealthCheck', + rpcArgs + ); + return new ProjectResource(result, this._client); + } + + /** Adds a health check by key */ + withHealthCheck(key: string): ProjectResourcePromise { + return new ProjectResourcePromise(this._withHealthCheckInternal(key)); + } + /** @internal */ private async _withHttpHealthCheckInternal(path?: string, statusCode?: number, endpointName?: string): Promise { const rpcArgs: Record = { builder: this._handle }; @@ -3888,6 +4076,11 @@ export class ProjectResourcePromise implements PromiseLike { return new ProjectResourcePromise(this._promise.then(obj => obj.waitForCompletion(dependency, options))); } + /** Adds a health check by key */ + withHealthCheck(key: string): ProjectResourcePromise { + return new ProjectResourcePromise(this._promise.then(obj => obj.withHealthCheck(key))); + } + /** Adds an HTTP health check */ withHttpHealthCheck(options?: WithHttpHealthCheckOptions): ProjectResourcePromise { return new ProjectResourcePromise(this._promise.then(obj => obj.withHttpHealthCheck(options))); @@ -4027,6 +4220,21 @@ export class Resource extends ResourceBuilderBase { return new ResourcePromise(this._withExplicitStartInternal()); } + /** @internal */ + private async _withHealthCheckInternal(key: string): Promise { + const rpcArgs: Record = { builder: this._handle, key }; + const result = await this._client.invokeCapability( + 'Aspire.Hosting/withHealthCheck', + rpcArgs + ); + return new Resource(result, this._client); + } + + /** Adds a health check by key */ + withHealthCheck(key: string): ResourcePromise { + return new ResourcePromise(this._withHealthCheckInternal(key)); + } + /** @internal */ private async _withCommandInternal(name: string, displayName: string, executeCommand: (arg: ExecuteCommandContext) => Promise, commandOptions?: CommandOptions): Promise { const executeCommandId = registerCallback(async (argData: unknown) => { @@ -4120,6 +4328,11 @@ export class ResourcePromise implements PromiseLike { return new ResourcePromise(this._promise.then(obj => obj.withExplicitStart())); } + /** Adds a health check by key */ + withHealthCheck(key: string): ResourcePromise { + return new ResourcePromise(this._promise.then(obj => obj.withHealthCheck(key))); + } + /** Adds a resource command */ withCommand(name: string, displayName: string, executeCommand: (arg: ExecuteCommandContext) => Promise, options?: WithCommandOptions): ResourcePromise { return new ResourcePromise(this._promise.then(obj => obj.withCommand(name, displayName, executeCommand, options))); diff --git a/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/.modules/transport.ts b/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/.modules/transport.ts index 6b9a4acae98..7bddd74beff 100644 --- a/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/.modules/transport.ts +++ b/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/.modules/transport.ts @@ -263,10 +263,27 @@ export function registerCallback( if (argArray.length > 0) { // Spread positional arguments to callback - return await callback(...argArray); + const result = await callback(...argArray); + // DTO writeback protocol: when a void callback returns undefined, we + // return the original args object so the .NET host can detect property + // mutations made by the callback and apply them back to the original + // C# DTO objects. DTO args are plain JS objects (not Handle wrappers), + // so any property changes the callback made are reflected in args. + // + // Non-void callbacks (result !== undefined) return their actual result. + // The .NET side only activates writeback for void delegates whose + // parameters include [AspireDto] types — all other cases discard the + // returned args object, so the extra wire payload is harmless. + // + // IMPORTANT: callbacks that intentionally return undefined will also + // trigger this path. For non-void delegate types, the C# proxy uses + // a result-unmarshalling path (not writeback), so returning args will + // cause an unmarshal error. Void callbacks should never return a + // meaningful value; non-void callbacks should always return one. + return result !== undefined ? result : args; } - // No positional params found - call with no args + // No positional params found — nothing to write back return await callback(); } diff --git a/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/apphost.ts b/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/apphost.ts index b3364fb53b9..837fb7d9a18 100644 --- a/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/apphost.ts +++ b/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/apphost.ts @@ -1,7 +1,30 @@ -import { createBuilder } from './.modules/aspire.js'; +import { createBuilder, GitHubModelName } from './.modules/aspire.js'; const builder = await createBuilder(); -const githubModel = builder.addGitHubModel('validation-model'); -githubModel.withApiKey({ secret: true }); -await builder.build().run(); +// 1) addGitHubModel — using the GitHubModelName enum +const githubModel = await builder.addGitHubModel("chat", GitHubModelName.OpenAIGpt4o); + +// 2) addGitHubModel — with organization parameter +const orgParam = await builder.addParameter("gh-org"); +const githubModelWithOrg = await builder.addGitHubModel("chat-org", GitHubModelName.OpenAIGpt4oMini, { organization: orgParam }); + +// 3) addGitHubModelById — using a model identifier string for models not in the enum +const customModel = await builder.addGitHubModelById("custom-chat", "custom-vendor/custom-model"); + +// 3) withApiKey — configure a custom API key parameter +const apiKey = await builder.addParameter("gh-api-key", { secret: true }); +await githubModel.withApiKey(apiKey); + +// 4) enableHealthCheck — integration-specific no-args health check +await githubModel.enableHealthCheck(); + +// 5) withReference — pass GitHubModelResource as a connection string source to a container +const container = await builder.addContainer("my-service", "mcr.microsoft.com/dotnet/samples:latest"); +await container.withReference(githubModel); + +// 6) withReference — pass GitHubModelResource as a source to another container with custom connection name +await container.withReference(githubModelWithOrg, { connectionName: "github-model-org" }); + +const app = await builder.build(); +await app.run(); diff --git a/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/tsconfig.json b/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/tsconfig.json index edf7302cc25..00f6d3b5743 100644 --- a/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/tsconfig.json +++ b/playground/polyglot/TypeScript/Aspire.Hosting.GitHub.Models/ValidationAppHost/tsconfig.json @@ -10,6 +10,6 @@ "outDir": "./dist", "rootDir": "." }, - "include": ["apphost.ts", ".modules/**/*.ts"], + "include": ["apphost.ts", ".modules/**/*.ts", ".modules/**/*.d.ts"], "exclude": ["node_modules"] } \ No newline at end of file diff --git a/src/Aspire.Hosting.GitHub.Models/GitHubModel.Generated.cs b/src/Aspire.Hosting.GitHub.Models/GitHubModel.Generated.cs index 32d0c14987f..d685f4a0be5 100644 --- a/src/Aspire.Hosting.GitHub.Models/GitHubModel.Generated.cs +++ b/src/Aspire.Hosting.GitHub.Models/GitHubModel.Generated.cs @@ -269,4 +269,276 @@ public static partial class XAI /// public static readonly GitHubModel Grok3Mini = new() { Id = "xai/grok-3-mini" }; } + + /// + /// Gets the model identifier string for the specified . + /// + internal static string GetModelId(GitHubModelName name) => name switch + { + GitHubModelName.AI21Jamba15Large => "ai21-labs/ai21-jamba-1.5-large", + GitHubModelName.CohereCommandA => "cohere/cohere-command-a", + GitHubModelName.CohereCommandR082024 => "cohere/cohere-command-r-08-2024", + GitHubModelName.CohereCommandRPlus082024 => "cohere/cohere-command-r-plus-08-2024", + GitHubModelName.DeepSeekR1 => "deepseek/deepseek-r1", + GitHubModelName.DeepSeekR10528 => "deepseek/deepseek-r1-0528", + GitHubModelName.DeepSeekV30324 => "deepseek/deepseek-v3-0324", + GitHubModelName.Llama4Maverick17B128EInstructFP8 => "meta/llama-4-maverick-17b-128e-instruct-fp8", + GitHubModelName.Llama4Scout17B16EInstruct => "meta/llama-4-scout-17b-16e-instruct", + GitHubModelName.Llama3211BVisionInstruct => "meta/llama-3.2-11b-vision-instruct", + GitHubModelName.Llama3290BVisionInstruct => "meta/llama-3.2-90b-vision-instruct", + GitHubModelName.Llama3370BInstruct => "meta/llama-3.3-70b-instruct", + GitHubModelName.MetaLlama31405BInstruct => "meta/meta-llama-3.1-405b-instruct", + GitHubModelName.MetaLlama318BInstruct => "meta/meta-llama-3.1-8b-instruct", + GitHubModelName.MaiDSR1 => "microsoft/mai-ds-r1", + GitHubModelName.Phi4 => "microsoft/phi-4", + GitHubModelName.Phi4MiniInstruct => "microsoft/phi-4-mini-instruct", + GitHubModelName.Phi4MiniReasoning => "microsoft/phi-4-mini-reasoning", + GitHubModelName.Phi4MultimodalInstruct => "microsoft/phi-4-multimodal-instruct", + GitHubModelName.Phi4Reasoning => "microsoft/phi-4-reasoning", + GitHubModelName.Codestral2501 => "mistral-ai/codestral-2501", + GitHubModelName.Ministral3B => "mistral-ai/ministral-3b", + GitHubModelName.MistralMedium32505 => "mistral-ai/mistral-medium-2505", + GitHubModelName.MistralSmall31 => "mistral-ai/mistral-small-2503", + GitHubModelName.OpenAIGpt41 => "openai/gpt-4.1", + GitHubModelName.OpenAIGpt41Mini => "openai/gpt-4.1-mini", + GitHubModelName.OpenAIGpt41Nano => "openai/gpt-4.1-nano", + GitHubModelName.OpenAIGpt4o => "openai/gpt-4o", + GitHubModelName.OpenAIGpt4oMini => "openai/gpt-4o-mini", + GitHubModelName.OpenAIGpt5 => "openai/gpt-5", + GitHubModelName.OpenAIGpt5ChatPreview => "openai/gpt-5-chat", + GitHubModelName.OpenAIGpt5Mini => "openai/gpt-5-mini", + GitHubModelName.OpenAIGpt5Nano => "openai/gpt-5-nano", + GitHubModelName.OpenAIO1 => "openai/o1", + GitHubModelName.OpenAIO1Mini => "openai/o1-mini", + GitHubModelName.OpenAIO1Preview => "openai/o1-preview", + GitHubModelName.OpenAIO3 => "openai/o3", + GitHubModelName.OpenAIO3Mini => "openai/o3-mini", + GitHubModelName.OpenAIO4Mini => "openai/o4-mini", + GitHubModelName.OpenAITextEmbedding3Large => "openai/text-embedding-3-large", + GitHubModelName.OpenAITextEmbedding3Small => "openai/text-embedding-3-small", + GitHubModelName.Grok3 => "xai/grok-3", + GitHubModelName.Grok3Mini => "xai/grok-3-mini", + _ => throw new System.ArgumentOutOfRangeException(nameof(name), name, "Unknown GitHub model name.") + }; +} + +/// +/// Enumerates known GitHub model names for polyglot app host support. +/// +internal enum GitHubModelName +{ + /// + /// A 398B parameters (94B active) multilingual model, offering a 256K long context window, function calling, structured output, and grounded generation. + /// + AI21Jamba15Large, + + /// + /// Command A is a highly efficient generative model that excels at agentic and multilingual use cases. + /// + CohereCommandA, + + /// + /// Command R is a scalable generative model targeting RAG and Tool Use to enable production-scale AI for enterprise. + /// + CohereCommandR082024, + + /// + /// Command R+ is a state-of-the-art RAG-optimized model designed to tackle enterprise-grade workloads. + /// + CohereCommandRPlus082024, + + /// + /// DeepSeek-R1 excels at reasoning tasks using a step-by-step training process, such as language, scientific reasoning, and coding tasks. + /// + DeepSeekR1, + + /// + /// The DeepSeek R1 0528 model has improved reasoning capabilities, this version also offers a reduced hallucination rate, enhanced support for function calling, and better experience for vibe coding. + /// + DeepSeekR10528, + + /// + /// DeepSeek-V3-0324 demonstrates notable improvements over its predecessor, DeepSeek-V3, in several key aspects, including enhanced reasoning, improved function calling, and superior code generation capabilities. + /// + DeepSeekV30324, + + /// + /// Llama 4 Maverick 17B 128E Instruct FP8 is great at precise image understanding and creative writing, offering high quality at a lower price compared to Llama 3.3 70B + /// + Llama4Maverick17B128EInstructFP8, + + /// + /// Llama 4 Scout 17B 16E Instruct is great at multi-document summarization, parsing extensive user activity for personalized tasks, and reasoning over vast codebases. + /// + Llama4Scout17B16EInstruct, + + /// + /// Excels in image reasoning capabilities on high-res images for visual understanding apps. + /// + Llama3211BVisionInstruct, + + /// + /// Advanced image reasoning capabilities for visual understanding agentic apps. + /// + Llama3290BVisionInstruct, + + /// + /// Llama 3.3 70B Instruct offers enhanced reasoning, math, and instruction following with performance comparable to Llama 3.1 405B. + /// + Llama3370BInstruct, + + /// + /// The Llama 3.1 instruction tuned text only models are optimized for multilingual dialogue use cases and outperform many of the available open source and closed chat models on common industry benchmarks. + /// + MetaLlama31405BInstruct, + + /// + /// The Llama 3.1 instruction tuned text only models are optimized for multilingual dialogue use cases and outperform many of the available open source and closed chat models on common industry benchmarks. + /// + MetaLlama318BInstruct, + + /// + /// MAI-DS-R1 is a DeepSeek-R1 reasoning model that has been post-trained by the Microsoft AI team to fill in information gaps in the previous version of the model and improve its harm protections while maintaining R1 reasoning capabilities. + /// + MaiDSR1, + + /// + /// Phi-4 14B, a highly capable model for low latency scenarios. + /// + Phi4, + + /// + /// 3.8B parameters Small Language Model outperforming larger models in reasoning, math, coding, and function-calling + /// + Phi4MiniInstruct, + + /// + /// Lightweight math reasoning model optimized for multi-step problem solving + /// + Phi4MiniReasoning, + + /// + /// First small multimodal model to have 3 modality inputs (text, audio, image), excelling in quality and efficiency + /// + Phi4MultimodalInstruct, + + /// + /// State-of-the-art open-weight reasoning model. + /// + Phi4Reasoning, + + /// + /// Codestral 25.01 by Mistral AI is designed for code generation, supporting 80+ programming languages, and optimized for tasks like code completion and fill-in-the-middle + /// + Codestral2501, + + /// + /// Ministral 3B is a state-of-the-art Small Language Model (SLM) optimized for edge computing and on-device applications. As it is designed for low-latency and compute-efficient inference, it it also the perfect model for standard GenAI applications that have + /// + Ministral3B, + + /// + /// Mistral Medium 3 is an advanced Large Language Model (LLM) with state-of-the-art reasoning, knowledge, coding and vision capabilities. + /// + MistralMedium32505, + + /// + /// Enhanced Mistral Small 3 with multimodal capabilities and a 128k context length. + /// + MistralSmall31, + + /// + /// gpt-4.1 outperforms gpt-4o across the board, with major gains in coding, instruction following, and long-context understanding + /// + OpenAIGpt41, + + /// + /// gpt-4.1-mini outperform gpt-4o-mini across the board, with major gains in coding, instruction following, and long-context handling + /// + OpenAIGpt41Mini, + + /// + /// gpt-4.1-nano provides gains in coding, instruction following, and long-context handling along with lower latency and cost + /// + OpenAIGpt41Nano, + + /// + /// OpenAI's most advanced multimodal model in the gpt-4o family. Can handle both text and image inputs. + /// + OpenAIGpt4o, + + /// + /// An affordable, efficient AI solution for diverse text and image tasks. + /// + OpenAIGpt4oMini, + + /// + /// gpt-5 is designed for logic-heavy and multi-step tasks. + /// + OpenAIGpt5, + + /// + /// gpt-5-chat (preview) is an advanced, natural, multimodal, and context-aware conversations for enterprise applications. + /// + OpenAIGpt5ChatPreview, + + /// + /// gpt-5-mini is a lightweight version for cost-sensitive applications. + /// + OpenAIGpt5Mini, + + /// + /// gpt-5-nano is optimized for speed, ideal for applications requiring low latency. + /// + OpenAIGpt5Nano, + + /// + /// Focused on advanced reasoning and solving complex problems, including math and science tasks. Ideal for applications that require deep contextual understanding and agentic workflows. + /// + OpenAIO1, + + /// + /// Smaller, faster, and 80% cheaper than o1-preview, performs well at code generation and small context operations. + /// + OpenAIO1Mini, + + /// + /// Focused on advanced reasoning and solving complex problems, including math and science tasks. Ideal for applications that require deep contextual understanding and agentic workflows. + /// + OpenAIO1Preview, + + /// + /// o3 includes significant improvements on quality and safety while supporting the existing features of o1 and delivering comparable or better performance. + /// + OpenAIO3, + + /// + /// o3-mini includes the o1 features with significant cost-efficiencies for scenarios requiring high performance. + /// + OpenAIO3Mini, + + /// + /// o4-mini includes significant improvements on quality and safety while supporting the existing features of o3-mini and delivering comparable or better performance. + /// + OpenAIO4Mini, + + /// + /// Text-embedding-3 series models are the latest and most capable embedding model from OpenAI. + /// + OpenAITextEmbedding3Large, + + /// + /// Text-embedding-3 series models are the latest and most capable embedding model from OpenAI. + /// + OpenAITextEmbedding3Small, + + /// + /// Grok 3 is xAI's debut model, pretrained by Colossus at supermassive scale to excel in specialized domains like finance, healthcare, and the law. + /// + Grok3, + + /// + /// Grok 3 Mini is a lightweight model that thinks before responding. Trained on mathematic and scientific problems, it is great for logic-based tasks. + /// + Grok3Mini, } diff --git a/src/Aspire.Hosting.GitHub.Models/GitHubModelsExtensions.cs b/src/Aspire.Hosting.GitHub.Models/GitHubModelsExtensions.cs index 4d7ac511840..65138394114 100644 --- a/src/Aspire.Hosting.GitHub.Models/GitHubModelsExtensions.cs +++ b/src/Aspire.Hosting.GitHub.Models/GitHubModelsExtensions.cs @@ -22,7 +22,7 @@ public static class GitHubModelsExtensions /// The model name to use with GitHub Models. /// The organization login associated with the organization to which the request is to be attributed. /// A reference to the . - [AspireExport("addGitHubModel1", MethodName = "addGitHubModel", Description = "Adds a GitHub Model resource to the distributed application model.")] + [AspireExportIgnore(Reason = "The polyglot overload uses the GitHubModelName enum instead. See the internal AddGitHubModel(GitHubModelName) overload.")] public static IResourceBuilder AddGitHubModel(this IDistributedApplicationBuilder builder, [ResourceName] string name, string model, IResourceBuilder? organization = null) { ArgumentNullException.ThrowIfNull(builder); @@ -89,6 +89,7 @@ await evt.Eventing.PublishAsync(new ConnectionStringAvailableEvent(r, evt.Servic /// The organization login associated with the organization to which the request is to be attributed. /// A reference to the . /// + /// This overload is not available in polyglot app hosts. Use the string-based overload instead. /// /// Create a GitHub Model resource for the Microsoft Phi-3 Medium Instruct model: /// @@ -98,7 +99,7 @@ await evt.Eventing.PublishAsync(new ConnectionStringAvailableEvent(r, evt.Servic /// /// /// - [AspireExport("addGitHubModel2", MethodName = "addGitHubModel", Description = "Adds a GitHub Model resource to the distributed application model.")] + [AspireExportIgnore(Reason = "GitHubModel is a .NET-specific descriptor type not compatible with ATS. Use the GitHubModelName enum-based overload instead.")] public static IResourceBuilder AddGitHubModel(this IDistributedApplicationBuilder builder, [ResourceName] string name, GitHubModel model, IResourceBuilder? organization = null) { ArgumentNullException.ThrowIfNull(model); @@ -106,6 +107,34 @@ public static IResourceBuilder AddGitHubModel(this IDistrib return AddGitHubModel(builder, name, model.Id, organization); } + /// + /// Adds a GitHub Model resource to the application model using a known . + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The known model name from the enumeration. + /// The organization login associated with the organization to which the request is to be attributed. + /// A reference to the . + [AspireExport("addGitHubModel", Description = "Adds a GitHub Model resource to the distributed application model.")] + internal static IResourceBuilder AddGitHubModel(this IDistributedApplicationBuilder builder, [ResourceName] string name, GitHubModelName model, IResourceBuilder? organization = null) + { + return AddGitHubModel(builder, name, GitHubModel.GetModelId(model), organization); + } + + /// + /// Adds a GitHub Model resource to the application model using a model identifier string. + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The model identifier string, for example "openai/gpt-4o". + /// The organization login associated with the organization to which the request is to be attributed. + /// A reference to the . + [AspireExport("addGitHubModelById", Description = "Adds a GitHub Model resource using a model identifier string.")] + internal static IResourceBuilder AddGitHubModelById(this IDistributedApplicationBuilder builder, [ResourceName] string name, string modelId, IResourceBuilder? organization = null) + { + return AddGitHubModel(builder, name, modelId, organization); + } + /// /// Configures the API key for the GitHub Model resource from a parameter. /// @@ -156,7 +185,7 @@ public static IResourceBuilder WithApiKey(this IResourceBui /// the model is not working as expected. Furthermore, the health check will run a single time per application instance. /// /// - [AspireExport("withHealthCheck", Description = "Adds a health check for the GitHub Model resource.")] + [AspireExport("enableHealthCheck", Description = "Adds a health check for the GitHub Model resource.")] public static IResourceBuilder WithHealthCheck(this IResourceBuilder builder) { ArgumentNullException.ThrowIfNull(builder); diff --git a/src/Aspire.Hosting.GitHub.Models/tools/GenModel.cs b/src/Aspire.Hosting.GitHub.Models/tools/GenModel.cs index 782e30dbf56..e3c79a9aa29 100644 --- a/src/Aspire.Hosting.GitHub.Models/tools/GenModel.cs +++ b/src/Aspire.Hosting.GitHub.Models/tools/GenModel.cs @@ -15,12 +15,6 @@ var ghCode = GitHubModelClassGenerator.GenerateCode("Aspire.Hosting.GitHub", ghModels); File.WriteAllText(Path.Combine("..", "GitHubModel.Generated.cs"), ghCode); Console.WriteLine("Generated GitHub model descriptors written to GitHubModel.Generated.cs"); -Console.WriteLine("\nGitHub Model data:"); -Console.WriteLine(JsonSerializer.Serialize(ghModels, new JsonSerializerOptions -{ - WriteIndented = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull -})); public sealed class GitHubModel { @@ -82,8 +76,11 @@ public static string GenerateCode(string ns, List models) sb.AppendLine("/// "); sb.AppendLine("public partial class GitHubModel"); sb.AppendLine("{"); - var groups = models.Where(m => !string.IsNullOrEmpty(m.Publisher) && !string.IsNullOrEmpty(m.Name)) - .GroupBy(m => m.Publisher) + var validModels = models.Where(m => !string.IsNullOrEmpty(m.Publisher) && !string.IsNullOrEmpty(m.Name)) + .OrderBy(m => m.Publisher, StringComparer.OrdinalIgnoreCase) + .ThenBy(m => m.Name, StringComparer.OrdinalIgnoreCase) + .ToList(); + var groups = validModels.GroupBy(m => m.Publisher) .OrderBy(g => g.Key, StringComparer.OrdinalIgnoreCase); var firstClass = true; foreach (var g in groups) @@ -121,6 +118,47 @@ public static string GenerateCode(string ns, List models) } sb.AppendLine(" }"); } + sb.AppendLine(); + + // Generate internal conversion method + sb.AppendLine(" /// "); + sb.AppendLine(" /// Gets the model identifier string for the specified ."); + sb.AppendLine(" /// "); + sb.AppendLine(" internal static string GetModelId(GitHubModelName name) => name switch"); + sb.AppendLine(" {"); + foreach (var m in validModels) + { + var prop = ToPascalCase(m.Name); + sb.AppendLine(CultureInfo.InvariantCulture, $" GitHubModelName.{prop} => \"{Esc(m.Id)}\","); + } + sb.AppendLine(" _ => throw new System.ArgumentOutOfRangeException(nameof(name), name, \"Unknown GitHub model name.\")"); + sb.AppendLine(" };"); + sb.AppendLine("}"); + sb.AppendLine(); + + // Generate internal enum + sb.AppendLine("/// "); + sb.AppendLine("/// Enumerates known GitHub model names for polyglot app host support."); + sb.AppendLine("/// "); + sb.AppendLine("internal enum GitHubModelName"); + sb.AppendLine("{"); + var firstEnum = true; + foreach (var m in validModels) + { + if (!firstEnum) + { + sb.AppendLine(); + } + + firstEnum = false; + + var prop = ToPascalCase(m.Name); + var desc = EscapeXml(Clean(m.Summary ?? $"Descriptor for {m.Name}")); + sb.AppendLine(" /// "); + sb.AppendLine(CultureInfo.InvariantCulture, $" /// {desc}"); + sb.AppendLine(" /// "); + sb.AppendLine(CultureInfo.InvariantCulture, $" {prop},"); + } sb.AppendLine("}"); return sb.ToString(); }