-
Notifications
You must be signed in to change notification settings - Fork 889
Add agentic workflows to remain aligned with Agent Framework releases #7614
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jeffhandley
wants to merge
9
commits into
dotnet:main
Choose a base branch
from
jeffhandley:jeffhandley/aiagent-webapi-workflow
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 8 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
d492e9e
Introduce the update-agent-framework-template skill
jeffhandley c960509
Introduce the Agent Framework orchestrator and feed discovery
jeffhandley e9e1c65
Introduce the worker workflow with fresh and incremental updates
jeffhandley 4c580e0
Route Agent Framework worker safe outputs through the CLI proxy
jeffhandley bba4ef1
Support taking over a human-bootstrapped PR that passes the label and…
jeffhandley 3a2929b
Support incorporating review feedback on the maintained PR
jeffhandley ebd5dd7
Evaluate Agent Framework changes across releases and keep consumption…
jeffhandley 1ec1ab4
Prevent invalid MEAI workflow safe outputs
jeffhandley ade6b74
Address Copilot feedback
jeffhandley File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| #!/usr/bin/env dotnet | ||
| #:package NuGet.Protocol@6.12.1 | ||
| #:property NuGetAudit=false | ||
| #:property NoWarn=IL2026;IL3050 | ||
| #:property JsonSerializerIsReflectionEnabledByDefault=true | ||
| #:property ManagePackageVersionsCentrally=false | ||
|
|
||
| // Deterministic discovery of the newest coherent Microsoft Agent Framework release on the | ||
| // dnceng "dotnet-public" NuGet feed, driven entirely by the feed. Framework-general: it carries | ||
| // no consumer-specific (e.g. project-template) knowledge. It emits the release version, its | ||
| // date-stamp, and every Microsoft.Agents.AI* family package's version at that release (plus each | ||
| // package's newest-overall and newest-stable). Consumers -- currently the aiagent-webapi project | ||
| // template worker -- map this signal onto their own package subset and files. | ||
| // | ||
| // The family is enumerated from the feed (PackageSearchResource, which sends semVerLevel=2.0.0 so | ||
| // preview/alpha-only packages are visible) unioned with a known-id seed so a search hiccup can | ||
| // never silently drop a package. AutoCompleteResource is NOT used: AzDO feeds expose no | ||
| // SearchAutocompleteService, so IdStartsWith throws at runtime. Version selection uses NuGetVersion | ||
| // / VersionComparer for correct SemVer 2.0 ordering, so the feed's return order is irrelevant and | ||
| // the anomalous 0.0.1-preview.* entry (present across most of the family) can never win. | ||
| // | ||
| // When an output-file path is passed as the first argument the JSON array is written there (so the | ||
| // "dotnet run" build/restore output on stdout never contaminates the capture); otherwise it is | ||
| // written to stdout. Notices/warnings/errors go to stderr as GitHub Actions workflow commands. | ||
|
|
||
| using System.Text.Json; | ||
| using NuGet.Common; | ||
| using NuGet.Protocol; | ||
| using NuGet.Protocol.Core.Types; | ||
| using NuGet.Versioning; | ||
|
|
||
| const string Channel = "dotnet-public"; | ||
| const string FamilyPrefix = "Microsoft.Agents.AI"; | ||
| const string Anchor = "Microsoft.Agents.AI"; // always publishes a stable release | ||
| const string ExcludeId = "Microsoft.Agents.AI.ProjectTemplates"; // consumer package, not framework | ||
|
|
||
| // Known family ids (determinism seed; search adds any new ones automatically). | ||
| string[] seed = | ||
| [ | ||
| "Microsoft.Agents.AI", "Microsoft.Agents.AI.Abstractions", "Microsoft.Agents.AI.OpenAI", | ||
| "Microsoft.Agents.AI.Workflows", "Microsoft.Agents.AI.Workflows.Generators", | ||
| "Microsoft.Agents.AI.DevUI", "Microsoft.Agents.AI.Hosting", | ||
| "Microsoft.Agents.AI.Foundry", "Microsoft.Agents.AI.Foundry.Hosting", | ||
| "Microsoft.Agents.AI.Hosting.OpenAI", | ||
| ]; | ||
|
|
||
| static string FeedIndex(string channel) => | ||
| $"https://pkgs.dev.azure.com/dnceng/public/_packaging/{channel}/nuget/v3/index.json"; | ||
| static void Notice(string m) => Console.Error.WriteLine($"::notice::{m}"); | ||
| static void Warn(string m) => Console.Error.WriteLine($"::warning::{m}"); | ||
| static void Fail(string m) | ||
| { | ||
| Console.Error.WriteLine($"::error::{m}"); | ||
| Environment.Exit(1); | ||
| } | ||
|
|
||
| var repo = Repository.Factory.GetCoreV3(FeedIndex(Channel)); | ||
| var cache = new SourceCacheContext { NoCache = true }; // see just-published builds immediately | ||
| var log = NullLogger.Instance; | ||
| var ct = CancellationToken.None; | ||
|
|
||
| // ---- enumerate the family: search (semVerLevel=2.0.0, page-by-count) UNION seed -------------- | ||
| async Task<SortedSet<string>> DiscoverFamily() | ||
| { | ||
| var ids = new SortedSet<string>(seed, StringComparer.OrdinalIgnoreCase); | ||
| try | ||
| { | ||
| var search = await repo.GetResourceAsync<PackageSearchResource>(); // NOT AutoCompleteResource (throws on AzDO) | ||
| var filter = new SearchFilter(includePrerelease: true); | ||
| int skip = 0, take = 100; | ||
| while (true) | ||
| { | ||
| var page = (await search.SearchAsync(FamilyPrefix, filter, skip, take, log, ct)).ToList(); | ||
| foreach (var r in page) | ||
| if (r.Identity.Id.StartsWith(FamilyPrefix, StringComparison.OrdinalIgnoreCase)) | ||
| ids.Add(r.Identity.Id); | ||
| if (page.Count < take) break; // AzDO totalHits is always "0"; page by returned count | ||
| skip += take; | ||
| } | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| Warn($"feed search unavailable ({ex.GetType().Name}: {ex.Message}); falling back to seed ids only."); | ||
| } | ||
| ids.RemoveWhere(id => id.Equals(ExcludeId, StringComparison.OrdinalIgnoreCase)); | ||
| return ids; | ||
| } | ||
|
|
||
| // All published versions of a package on the feed, minus the anomalous 0.0.1-preview.* entry. | ||
| // Retries a few transient feed errors before treating the package as having no builds. | ||
| async Task<IReadOnlyList<NuGetVersion>> AllVersions(string id) | ||
| { | ||
| Exception? last = null; | ||
| for (var attempt = 1; attempt <= 3; attempt++) | ||
| { | ||
| try | ||
| { | ||
| var res = await repo.GetResourceAsync<FindPackageByIdResource>(); | ||
| var vs = await res.GetAllVersionsAsync(id, cache, log, ct); | ||
| return vs.Where(v => v.Major > 0).ToList(); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| last = ex; | ||
| if (attempt < 3) await Task.Delay(2000); | ||
| } | ||
| } | ||
| Warn($"could not read versions for '{id}' after 3 attempts: {last?.Message}"); | ||
| return []; | ||
| } | ||
|
|
||
| var family = await DiscoverFamily(); | ||
| var versionsById = new Dictionary<string, IReadOnlyList<NuGetVersion>>(StringComparer.OrdinalIgnoreCase); | ||
| foreach (var id in family) | ||
| versionsById[id] = await AllVersions(id); | ||
|
|
||
| // ---- release_version = newest STABLE of the anchor ------------------------------------------ | ||
| var anchorVersions = versionsById.TryGetValue(Anchor, out var av) ? av : []; | ||
| var release = anchorVersions.Where(v => !v.IsPrerelease).Max(); | ||
| if (release is null) | ||
| Fail($"No stable {Anchor} version found on {Channel}; aborting rather than emitting an empty signal."); | ||
|
|
||
| bool AtRelease(NuGetVersion v) => | ||
| v.Major == release!.Major && v.Minor == release.Minor && v.Patch == release.Patch; | ||
|
|
||
| static string Tier(NuGetVersion v) => | ||
| !v.IsPrerelease ? "stable" | ||
| : v.Release.StartsWith("alpha", StringComparison.OrdinalIgnoreCase) ? "alpha" | ||
| : v.Release.StartsWith("preview", StringComparison.OrdinalIgnoreCase) ? "preview" | ||
| : v.Release.Split('.')[0].ToLowerInvariant(); | ||
|
|
||
| // ---- per-package: newest at the coherent release (any tier), newest overall, newest stable --- | ||
| var packages = new SortedDictionary<string, object?>(StringComparer.Ordinal); | ||
| string? releaseDate = null; | ||
| foreach (var (id, vs) in versionsById) | ||
| { | ||
| var atRelease = vs.Where(AtRelease).Max(); | ||
| var newest = vs.Count > 0 ? vs.Max() : null; | ||
| var newestStable = vs.Where(v => !v.IsPrerelease).Max(); | ||
|
jeffhandley marked this conversation as resolved.
Outdated
|
||
|
|
||
| if (releaseDate is null && atRelease is { IsPrerelease: true }) | ||
| { | ||
| var parts = atRelease.Release.Split('.'); // e.g. "preview", "260703", "1" | ||
| if (parts.Length >= 2) releaseDate = parts[1]; | ||
| } | ||
|
|
||
| packages[id] = new Dictionary<string, string?> | ||
| { | ||
| ["tier"] = atRelease is null ? null : Tier(atRelease), | ||
| ["at_release"] = atRelease?.ToNormalizedString(), | ||
| ["latest"] = newest?.ToNormalizedString(), | ||
| ["latest_stable"] = newestStable?.ToNormalizedString(), | ||
| }; | ||
| } | ||
|
|
||
| Notice($"Agent Framework release {release} (date {releaseDate ?? "n/a"}) across {family.Count} packages on {Channel}."); | ||
|
|
||
| var signal = new Dictionary<string, object?> | ||
| { | ||
| ["source_feed"] = Channel, | ||
| ["release_version"] = release!.ToNormalizedString(), // e.g. "1.13.0" | ||
| ["release_date"] = releaseDate, // e.g. "260703" | ||
| ["packages"] = packages, | ||
| }; | ||
|
|
||
| // Emit a single-element array so the orchestrator's matrix fans out over one target (the release). | ||
| var json = JsonSerializer.Serialize(new[] { signal }); | ||
| if (args.Length > 0) | ||
| File.WriteAllText(args[0], json); | ||
| else | ||
| Console.WriteLine(json); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.