Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions .github/workflows/preview_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
# 4:19 AM UTC every day. A random time to avoid peak times of GitHub Actions.
- cron: '19 4 * * *'
permissions:
actions: read
contents: read
packages: write
env:
Expand All @@ -19,10 +20,44 @@ jobs:
- name: Check if should publish
id: check-publish
shell: pwsh
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
$hasCommitFromLastDay = ![string]::IsNullOrEmpty((git log --oneline --since '24 hours ago'))
Write-Output "Commits found in the last 24 hours: $hasCommitFromLastDay."
$shouldPublish = ($hasCommitFromLastDay -and '${{ github.event_name }}' -eq 'schedule') -or ('${{ github.event_name }}' -eq 'workflow_dispatch')
$eventName = '${{ github.event_name }}'

if ($eventName -eq 'workflow_dispatch')
{
Write-Output 'Manual preview release requested. Publishing unconditionally.'
"should-publish=true" >> $Env:GITHUB_OUTPUT
exit 0
}

$headers = @{
Authorization = "Bearer $Env:GITHUB_TOKEN"
Accept = 'application/vnd.github+json'
'X-GitHub-Api-Version' = '2022-11-28'
}

$workflowRunsUrl = 'https://api.github.com/repos/${{ github.repository }}/actions/workflows/preview_ci.yml/runs?status=success&branch=${{ github.ref_name }}&per_page=20'
$response = Invoke-RestMethod -Uri $workflowRunsUrl -Headers $headers -Method Get
$previousRun = $response.workflow_runs |
Where-Object { $_.id -ne [int64]'${{ github.run_id }}' } |
Sort-Object created_at -Descending |
Select-Object -First 1

if ($null -eq $previousRun)
{
Write-Output 'No previous successful preview publish run found. Publishing.'
"should-publish=true" >> $Env:GITHUB_OUTPUT
exit 0
}

$hasNewCommitSinceLastRelease = $previousRun.head_sha -ne '${{ github.sha }}'
Write-Output "Last successful preview publish SHA: $($previousRun.head_sha)"
Write-Output "Current SHA: ${{ github.sha }}"
Write-Output "New commits since last preview publish: $hasNewCommitSinceLastRelease"

$shouldPublish = $eventName -eq 'schedule' -and $hasNewCommitSinceLastRelease
"should-publish=$($shouldPublish ? 'true' : 'false')" >> $Env:GITHUB_OUTPUT
- uses: actions/setup-node@v6
if: steps.check-publish.outputs.should-publish == 'true'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public interface IAIClientFactory
/// <returns>
/// A <see cref = "ValueTask{TResult}"/> representing the asynchronous operation, with the created <see cref = "IImageGenerator"/>.
/// </returns>

#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

ValueTask<IImageGenerator> CreateImageGeneratorAsync(string providerName, string connectionName, string deploymentName = null);
Expand All @@ -52,7 +52,7 @@ public interface IAIClientFactory
/// <returns>
/// A <see cref = "ValueTask{TResult}"/> representing the asynchronous operation, with the created <see cref = "ISpeechToTextClient"/>.
/// </returns>

#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

ValueTask<ISpeechToTextClient> CreateSpeechToTextClientAsync(string providerName, string connectionName, string deploymentName = null);
Expand All @@ -66,7 +66,7 @@ public interface IAIClientFactory
/// <returns>
/// A <see cref = "ValueTask{TResult}"/> representing the asynchronous operation, with the created <see cref = "ISpeechToTextClient"/>.
/// </returns>

#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

ValueTask<ISpeechToTextClient> CreateSpeechToTextClientAsync(AIDeployment deployment);
Expand All @@ -81,7 +81,7 @@ public interface IAIClientFactory
/// <returns>
/// A <see cref = "ValueTask{TResult}"/> representing the asynchronous operation, with the created <see cref = "ITextToSpeechClient"/>.
/// </returns>

#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

ValueTask<ITextToSpeechClient> CreateTextToSpeechClientAsync(string providerName, string connectionName, string deploymentName = null);
Expand All @@ -95,7 +95,7 @@ public interface IAIClientFactory
/// <returns>
/// A <see cref = "ValueTask{TResult}"/> representing the asynchronous operation, with the created <see cref = "ITextToSpeechClient"/>.
/// </returns>

#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

ValueTask<ITextToSpeechClient> CreateTextToSpeechClientAsync(AIDeployment deployment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public interface IAIClientProvider
/// <param name = "connection">The connection entry containing provider configuration.</param>
/// <param name = "deploymentName">The optional deployment name to use.</param>
/// <returns>A <see cref = "ValueTask{IImageGenerator}"/> representing the asynchronous operation.</returns>

#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

ValueTask<IImageGenerator> GetImageGeneratorAsync(AIProviderConnectionEntry connection, string deploymentName = null);
Expand All @@ -45,7 +45,7 @@ public interface IAIClientProvider
/// <param name = "connection">The connection entry containing provider configuration.</param>
/// <param name = "deploymentName">The optional deployment name to use.</param>
/// <returns>A <see cref = "ValueTask{ISpeechToTextClient}"/> representing the asynchronous operation.</returns>

#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

ValueTask<ISpeechToTextClient> GetSpeechToTextClientAsync(AIProviderConnectionEntry connection, string deploymentName = null);
Expand All @@ -57,7 +57,7 @@ public interface IAIClientProvider
/// <param name = "connection">The connection entry containing provider configuration.</param>
/// <param name = "deploymentName">The optional deployment name to use.</param>
/// <returns>A <see cref = "ValueTask{ITextToSpeechClient}"/> representing the asynchronous operation.</returns>

#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

ValueTask<ITextToSpeechClient> GetTextToSpeechClientAsync(AIProviderConnectionEntry connection, string deploymentName = null);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,6 @@ namespace CrestApps.Core.AI.Json;

public sealed class AIProviderConnectionConverter : JsonConverter<AIProviderConnectionEntry>
{
/// <summary>
/// Maps legacy configuration key names to their current equivalents.
/// </summary>
private static readonly Dictionary<string, string> _legacyKeyMappings = new(StringComparer.OrdinalIgnoreCase)
{
["DefaultDeploymentName"] = "ChatDeploymentName",
["DefaultChatDeploymentName"] = "ChatDeploymentName",
["DefaultUtilityDeploymentName"] = "UtilityDeploymentName",
["DefaultEmbeddingDeploymentName"] = "EmbeddingDeploymentName",
["DefaultImagesDeploymentName"] = "ImagesDeploymentName",
};

public override AIProviderConnectionEntry Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// Deserialize into a dictionary first.
Expand All @@ -28,16 +16,6 @@ public override AIProviderConnectionEntry Read(ref Utf8JsonReader reader, Type t
return null;
}

// Migrate legacy keys to current keys.
foreach (var (legacyKey, newKey) in _legacyKeyMappings)
{
if (dictionary.TryGetValue(legacyKey, out var value) && !dictionary.ContainsKey(newKey))
{
dictionary[newKey] = value;
dictionary.Remove(legacyKey);
}
}

return new AIProviderConnectionEntry(dictionary);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,27 @@ public override AIProviderConnection Read(ref Utf8JsonReader reader, Type typeTo
?? GetString(node, "ProviderName"),
Name = GetString(node, nameof(AIProviderConnection.Name)),
DisplayText = GetString(node, nameof(AIProviderConnection.DisplayText)),
#pragma warning disable CS0618 // Obsolete deployment name fields retained for backward compatibility
ChatDeploymentName = GetString(node, nameof(AIProviderConnection.ChatDeploymentName))
?? GetString(node, "DefaultDeploymentName"),
EmbeddingDeploymentName = GetString(node, nameof(AIProviderConnection.EmbeddingDeploymentName))
?? GetString(node, "DefaultEmbeddingDeploymentName"),
ImagesDeploymentName = GetString(node, nameof(AIProviderConnection.ImagesDeploymentName))
?? GetString(node, "DefaultImagesDeploymentName"),
UtilityDeploymentName = GetString(node, nameof(AIProviderConnection.UtilityDeploymentName))
?? GetString(node, "DefaultUtilityDeploymentName"),
#pragma warning restore CS0618
CreatedUtc = GetDateTime(node, nameof(AIProviderConnection.CreatedUtc)),
Author = GetString(node, nameof(AIProviderConnection.Author)),
OwnerId = GetString(node, nameof(AIProviderConnection.OwnerId)),
};

var propertyValues = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

if (node.TryGetPropertyValue(nameof(AIProviderConnection.Properties), out var propertiesNode)
&& propertiesNode is JsonObject properties)
&& propertiesNode is JsonObject propertiesObject)
{
// Detach from parent before deserializing.
node.Remove(nameof(AIProviderConnection.Properties));
connection.Properties = properties.Deserialize<Dictionary<string, object>>() ?? new Dictionary<string, object>();
foreach (var property in propertiesObject.Deserialize<Dictionary<string, object>>() ?? new Dictionary<string, object>())
{
propertyValues[property.Key] = property.Value;
}
}

if (propertyValues.Count > 0)
{
connection.Properties = propertyValues;
}

return connection;
Expand All @@ -58,12 +58,6 @@ public override void Write(Utf8JsonWriter writer, AIProviderConnection value, Js
WriteString(writer, nameof(AIProviderConnection.ClientName), value.ClientName);
WriteString(writer, nameof(AIProviderConnection.Name), value.Name);
WriteString(writer, nameof(AIProviderConnection.DisplayText), value.DisplayText);
#pragma warning disable CS0618 // Obsolete deployment name fields retained for backward compatibility
WriteString(writer, nameof(AIProviderConnection.ChatDeploymentName), value.ChatDeploymentName);
WriteString(writer, nameof(AIProviderConnection.EmbeddingDeploymentName), value.EmbeddingDeploymentName);
WriteString(writer, nameof(AIProviderConnection.ImagesDeploymentName), value.ImagesDeploymentName);
WriteString(writer, nameof(AIProviderConnection.UtilityDeploymentName), value.UtilityDeploymentName);
#pragma warning restore CS0618
writer.WriteString(nameof(AIProviderConnection.CreatedUtc), value.CreatedUtc);
WriteString(writer, nameof(AIProviderConnection.Author), value.Author);
WriteString(writer, nameof(AIProviderConnection.OwnerId), value.OwnerId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using CrestApps.Core.Services;

namespace CrestApps.Core.AI.Models;

public class AIDeployment : SourceCatalogEntry, INameAwareModel, ISourceAwareModel, ICloneable<AIDeployment>
{
private string _modelName;
Expand All @@ -11,7 +12,11 @@ public class AIDeployment : SourceCatalogEntry, INameAwareModel, ISourceAwareMod
/// This maps to a registered key in <c>AIOptions.Clients</c>.
/// For connection-based deployments, this is typically derived from the connection's <c>ClientName</c>.
/// </summary>
public string ClientName { get => Source; set => Source = value; }
public string ClientName
{
get => Source;
set => Source = value;
}

[Obsolete("Use ClientName instead. Retained for backward compatibility.")]
[JsonIgnore]
Expand All @@ -28,21 +33,30 @@ public class AIDeployment : SourceCatalogEntry, INameAwareModel, ISourceAwareMod
/// Gets or sets the provider-facing model or deployment name.
/// Falls back to <see cref = "Name"/> for backward compatibility with legacy records.
/// </summary>
public string ModelName { get => string.IsNullOrWhiteSpace(_modelName) ? Name : _modelName; set => _modelName = value?.Trim(); }
public string ModelName
{
get => string.IsNullOrWhiteSpace(_modelName)
? Name
: _modelName;
set => _modelName = value?.Trim();
}

public string ConnectionName { get; set; }
public string ConnectionNameAlias { get; set; }

/// <summary>
/// Gets or sets the capability types of this deployment (Chat, Utility, Embedding, Image, SpeechToText, TextToSpeech).
/// A deployment can support one or more capabilities.
/// </summary>
public AIDeploymentType Type { get; set; }

/// <summary>
/// Gets or sets whether this deployment is the default for its selected capability types
/// within its connection.
/// </summary>
public bool IsDefault { get; set; }
public DateTime CreatedUtc { get; set; }

public string Author { get; set; }

public string OwnerId { get; set; }

public bool SupportsType(AIDeploymentType type)
Expand All @@ -59,13 +73,11 @@ public AIDeployment Clone()
ModelName = _modelName,
Source = Source,
ConnectionName = ConnectionName,
ConnectionNameAlias = ConnectionNameAlias,
Type = Type,
IsDefault = IsDefault,
CreatedUtc = CreatedUtc,
Author = Author,
OwnerId = OwnerId,
Properties = Properties.Clone(),
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace CrestApps.Core.AI.Models;

public sealed class AIDeploymentCatalogOptions
{
public IList<string> DeploymentSections { get; } =
[
"CrestApps:AI:Deployments",
];
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace CrestApps.Core.AI.Models;

public static class AIDeploymentTypeExtensions
{
private static readonly AIDeploymentType _allSupportedTypes = Enum.GetValues<AIDeploymentType>().Where(type => type != AIDeploymentType.None).Aggregate(AIDeploymentType.None, static (current, type) => current | type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,6 @@ public sealed class AIProviderConnection : SourceCatalogEntry, INameAwareModel,

public string DisplayText { get; set; }

[Obsolete("Use typed AIDeployment records instead. This property is retained for backward compatibility and migration.")]
public string ChatDeploymentName { get; set; }

[Obsolete("Use typed AIDeployment records instead. This property is retained for backward compatibility and migration.")]
public string EmbeddingDeploymentName { get; set; }

[Obsolete("Use typed AIDeployment records instead. This property is retained for backward compatibility and migration.")]
public string ImagesDeploymentName { get; set; }

[Obsolete("Use typed AIDeployment records instead. This property is retained for backward compatibility and migration.")]
public string UtilityDeploymentName { get; set; }

[Obsolete("Use typed AIDeployment records instead. This property is retained for backward compatibility and migration.")]
public string SpeechToTextDeploymentName { get; set; }

/// <summary>
/// Gets or sets the technical name of the AI client implementation associated with this connection.
/// This maps to a registered key in <c>AIOptions.Clients</c>.
Expand All @@ -52,34 +37,6 @@ public string ProviderName

public string OwnerId { get; set; }

public string GetLegacyChatDeploymentName()
{
#pragma warning disable CS0618 // Type or member is obsolete
return ChatDeploymentName;
#pragma warning restore CS0618 // Type or member is obsolete
}

public string GetLegacyEmbeddingDeploymentName()
{
#pragma warning disable CS0618 // Type or member is obsolete
return EmbeddingDeploymentName;
#pragma warning restore CS0618 // Type or member is obsolete
}

public string GetLegacyImageDeploymentName()
{
#pragma warning disable CS0618 // Type or member is obsolete
return ImagesDeploymentName;
#pragma warning restore CS0618 // Type or member is obsolete
}

public string GetLegacyUtilityDeploymentName()
{
#pragma warning disable CS0618 // Type or member is obsolete
return UtilityDeploymentName;
#pragma warning restore CS0618 // Type or member is obsolete
}

public AIProviderConnection Clone()
{
return new AIProviderConnection
Expand All @@ -88,13 +45,6 @@ public AIProviderConnection Clone()
Source = Source,
Name = Name,
DisplayText = DisplayText,
#pragma warning disable CS0618 // Type or member is obsolete
ChatDeploymentName = ChatDeploymentName,
EmbeddingDeploymentName = EmbeddingDeploymentName,
ImagesDeploymentName = ImagesDeploymentName,
UtilityDeploymentName = UtilityDeploymentName,
SpeechToTextDeploymentName = SpeechToTextDeploymentName,
#pragma warning restore CS0618 // Type or member is obsolete
CreatedUtc = CreatedUtc,
Author = Author,
OwnerId = OwnerId,
Expand Down
Loading
Loading