Skip to content

Commit 21c0734

Browse files
Add native sub-agent manifest endpoints
Closes #89
1 parent 82327a8 commit 21c0734

5 files changed

Lines changed: 337 additions & 10 deletions

File tree

src/SkillServer/Endpoints.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,48 @@ private static void MapManifestEndpoints(this WebApplication app)
8888
? Results.NotFound()
8989
: Results.Json(skillVersion, SkillServerJsonContext.Default.NativeSkillVersionDetail);
9090
});
91+
92+
manifest.MapGet("/subagents/index.json", async (
93+
NativeManifestGenerator manifestGenerator,
94+
CancellationToken ct) =>
95+
{
96+
var index = await manifestGenerator.GenerateSubAgentIndexAsync(ct);
97+
return Results.Json(index, SkillServerJsonContext.Default.NativeSubAgentCollectionIndex);
98+
});
99+
100+
manifest.MapGet("/subagents/pages/{page}.json", async (
101+
string page,
102+
NativeManifestGenerator manifestGenerator,
103+
CancellationToken ct) =>
104+
{
105+
var subAgentPage = await manifestGenerator.GenerateSubAgentPageAsync(page, ct);
106+
return subAgentPage is null
107+
? Results.NotFound()
108+
: Results.Json(subAgentPage, SkillServerJsonContext.Default.NativeSubAgentCollectionPage);
109+
});
110+
111+
manifest.MapGet("/subagents/{subAgentName}/index.json", async (
112+
string subAgentName,
113+
NativeManifestGenerator manifestGenerator,
114+
CancellationToken ct) =>
115+
{
116+
var subAgent = await manifestGenerator.GenerateSubAgentIdentityAsync(subAgentName, ct);
117+
return subAgent is null
118+
? Results.NotFound()
119+
: Results.Json(subAgent, SkillServerJsonContext.Default.NativeSubAgentIdentityIndex);
120+
});
121+
122+
manifest.MapGet("/subagents/{subAgentName}/versions/{version}.json", async (
123+
string subAgentName,
124+
string version,
125+
NativeManifestGenerator manifestGenerator,
126+
CancellationToken ct) =>
127+
{
128+
var subAgentVersion = await manifestGenerator.GenerateSubAgentVersionAsync(subAgentName, version, ct);
129+
return subAgentVersion is null
130+
? Results.NotFound()
131+
: Results.Json(subAgentVersion, SkillServerJsonContext.Default.NativeSubAgentVersionDetail);
132+
});
91133
}
92134

93135
private static void MapSkillEndpoints(this WebApplication app)

src/SkillServer/Models/NativeManifest.cs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ public sealed record NativeRootManifestLinks
2929

3030
[JsonPropertyName("skills")]
3131
public required NativeManifestLink Skills { get; init; }
32+
33+
[JsonPropertyName("subagents")]
34+
public required NativeManifestLink SubAgents { get; init; }
3235
}
3336

3437
public sealed record NativeRootManifest
@@ -64,6 +67,18 @@ public sealed record NativeSkillCollectionIndex
6467
public required IReadOnlyList<NativeManifestPageLink> Pages { get; init; }
6568
}
6669

70+
public sealed record NativeSubAgentCollectionIndex
71+
{
72+
[JsonPropertyName("kind")]
73+
public string Kind { get; init; } = "subagent-index";
74+
75+
[JsonPropertyName("links")]
76+
public required NativeManifestSelfLinks Links { get; init; }
77+
78+
[JsonPropertyName("pages")]
79+
public required IReadOnlyList<NativeManifestPageLink> Pages { get; init; }
80+
}
81+
6782
public sealed record NativeVersionRange
6883
{
6984
[JsonPropertyName("min")]
@@ -91,6 +106,21 @@ public sealed record NativeSkillPageItem
91106
public required string Href { get; init; }
92107
}
93108

109+
public sealed record NativeSubAgentPageItem
110+
{
111+
[JsonPropertyName("name")]
112+
public required string Name { get; init; }
113+
114+
[JsonPropertyName("latestVersion")]
115+
public required string LatestVersion { get; init; }
116+
117+
[JsonPropertyName("versionRange")]
118+
public required NativeVersionRange VersionRange { get; init; }
119+
120+
[JsonPropertyName("href")]
121+
public required string Href { get; init; }
122+
}
123+
94124
public sealed record NativeSkillCollectionPage
95125
{
96126
[JsonPropertyName("kind")]
@@ -106,6 +136,21 @@ public sealed record NativeSkillCollectionPage
106136
public required NativeManifestSelfLinks Links { get; init; }
107137
}
108138

139+
public sealed record NativeSubAgentCollectionPage
140+
{
141+
[JsonPropertyName("kind")]
142+
public string Kind { get; init; } = "subagent-page";
143+
144+
[JsonPropertyName("range")]
145+
public required string Range { get; init; }
146+
147+
[JsonPropertyName("items")]
148+
public required IReadOnlyList<NativeSubAgentPageItem> Items { get; init; }
149+
150+
[JsonPropertyName("links")]
151+
public required NativeManifestSelfLinks Links { get; init; }
152+
}
153+
109154
public sealed record NativeSkillVersionLink
110155
{
111156
[JsonPropertyName("version")]
@@ -121,6 +166,21 @@ public sealed record NativeSkillVersionLink
121166
public required string Href { get; init; }
122167
}
123168

169+
public sealed record NativeSubAgentVersionLink
170+
{
171+
[JsonPropertyName("version")]
172+
public required string Version { get; init; }
173+
174+
[JsonPropertyName("publishedAt")]
175+
public required DateTimeOffset PublishedAt { get; init; }
176+
177+
[JsonPropertyName("digest")]
178+
public required string Digest { get; init; }
179+
180+
[JsonPropertyName("href")]
181+
public required string Href { get; init; }
182+
}
183+
124184
public sealed record NativeSkillIdentityIndex
125185
{
126186
[JsonPropertyName("kind")]
@@ -139,6 +199,24 @@ public sealed record NativeSkillIdentityIndex
139199
public required NativeManifestSelfLinks Links { get; init; }
140200
}
141201

202+
public sealed record NativeSubAgentIdentityIndex
203+
{
204+
[JsonPropertyName("kind")]
205+
public string Kind { get; init; } = "subagent";
206+
207+
[JsonPropertyName("name")]
208+
public required string Name { get; init; }
209+
210+
[JsonPropertyName("latestVersion")]
211+
public required string LatestVersion { get; init; }
212+
213+
[JsonPropertyName("versions")]
214+
public required IReadOnlyList<NativeSubAgentVersionLink> Versions { get; init; }
215+
216+
[JsonPropertyName("links")]
217+
public required NativeManifestSelfLinks Links { get; init; }
218+
}
219+
142220
public sealed record NativeSkillArtifact
143221
{
144222
[JsonPropertyName("name")]
@@ -183,3 +261,30 @@ public sealed record NativeSkillVersionDetail
183261
[JsonPropertyName("links")]
184262
public required NativeManifestSelfLinks Links { get; init; }
185263
}
264+
265+
public sealed record NativeSubAgentVersionDetail
266+
{
267+
[JsonPropertyName("kind")]
268+
public string Kind { get; init; } = "subagent-version";
269+
270+
[JsonPropertyName("name")]
271+
public required string Name { get; init; }
272+
273+
[JsonPropertyName("version")]
274+
public required string Version { get; init; }
275+
276+
[JsonPropertyName("type")]
277+
public string Type { get; init; } = SubAgentTypes.AgentMd;
278+
279+
[JsonPropertyName("description")]
280+
public required string Description { get; init; }
281+
282+
[JsonPropertyName("url")]
283+
public required string Url { get; init; }
284+
285+
[JsonPropertyName("digest")]
286+
public required string Digest { get; init; }
287+
288+
[JsonPropertyName("links")]
289+
public required NativeManifestSelfLinks Links { get; init; }
290+
}

src/SkillServer/Models/SkillServerJsonContext.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ namespace SkillServer.Models;
3737
[JsonSerializable(typeof(NativeSkillCollectionPage))]
3838
[JsonSerializable(typeof(NativeSkillIdentityIndex))]
3939
[JsonSerializable(typeof(NativeSkillVersionDetail))]
40+
[JsonSerializable(typeof(NativeSubAgentCollectionIndex))]
41+
[JsonSerializable(typeof(NativeSubAgentCollectionPage))]
42+
[JsonSerializable(typeof(NativeSubAgentIdentityIndex))]
43+
[JsonSerializable(typeof(NativeSubAgentVersionDetail))]
4044
[JsonSourceGenerationOptions(
4145
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
4246
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]

src/SkillServer/Services/NativeManifestGenerator.cs

Lines changed: 117 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,19 @@ namespace SkillServer.Services;
1111
public sealed class NativeManifestGenerator
1212
{
1313
private const string SkillsPageRange = "all";
14+
private const string SubAgentsPageRange = "all";
1415

15-
private readonly SkillRepository _repository;
16+
private readonly SkillRepository _skillRepository;
17+
private readonly SubAgentRepository _subAgentRepository;
1618
private readonly IConfiguration _configuration;
1719

18-
public NativeManifestGenerator(SkillRepository repository, IConfiguration configuration)
20+
public NativeManifestGenerator(
21+
SkillRepository skillRepository,
22+
SubAgentRepository subAgentRepository,
23+
IConfiguration configuration)
1924
{
20-
_repository = repository;
25+
_skillRepository = skillRepository;
26+
_subAgentRepository = subAgentRepository;
2127
_configuration = configuration;
2228
}
2329

@@ -30,7 +36,8 @@ public Task<NativeRootManifest> GenerateRootAsync(CancellationToken ct = default
3036
{
3137
Self = Link("/manifest.json"),
3238
RfcSkills = Link("/.well-known/agent-skills/index.json"),
33-
Skills = Link("/manifest/skills/index.json")
39+
Skills = Link("/manifest/skills/index.json"),
40+
SubAgents = Link("/manifest/subagents/index.json")
3441
}
3542
};
3643

@@ -57,12 +64,12 @@ public Task<NativeSkillCollectionIndex> GenerateSkillIndexAsync(CancellationToke
5764
if (!page.Equals(SkillsPageRange, StringComparison.OrdinalIgnoreCase))
5865
return null;
5966

60-
var latestVersions = await _repository.GetAllLatestVersionsWithMetadataAsync(ct: ct);
67+
var latestVersions = await _skillRepository.GetAllLatestVersionsWithMetadataAsync(ct: ct);
6168
var items = new List<NativeSkillPageItem>(latestVersions.Count);
6269

6370
foreach (var latest in latestVersions)
6471
{
65-
var versions = await _repository.GetAllVersionsAsync(latest.SkillId, ct);
72+
var versions = await _skillRepository.GetAllVersionsAsync(latest.SkillId, ct);
6673
if (versions.Count == 0)
6774
continue;
6875

@@ -91,11 +98,11 @@ public Task<NativeSkillCollectionIndex> GenerateSkillIndexAsync(CancellationToke
9198

9299
public async Task<NativeSkillIdentityIndex?> GenerateSkillIdentityAsync(string skillName, CancellationToken ct = default)
93100
{
94-
var skill = await _repository.GetSkillByNameAsync(skillName, ct);
101+
var skill = await _skillRepository.GetSkillByNameAsync(skillName, ct);
95102
if (skill is null)
96103
return null;
97104

98-
var versions = await _repository.GetAllVersionsAsync(skill.Id, ct);
105+
var versions = await _skillRepository.GetAllVersionsAsync(skill.Id, ct);
99106
if (versions.Count == 0)
100107
return null;
101108

@@ -117,11 +124,11 @@ public Task<NativeSkillCollectionIndex> GenerateSkillIndexAsync(CancellationToke
117124

118125
public async Task<NativeSkillVersionDetail?> GenerateSkillVersionAsync(string skillName, string version, CancellationToken ct = default)
119126
{
120-
var skill = await _repository.GetSkillByNameAsync(skillName, ct);
127+
var skill = await _skillRepository.GetSkillByNameAsync(skillName, ct);
121128
if (skill is null)
122129
return null;
123130

124-
var skillVersion = await _repository.GetVersionAsync(skill.Id, version, ct);
131+
var skillVersion = await _skillRepository.GetVersionAsync(skill.Id, version, ct);
125132
if (skillVersion is null)
126133
return null;
127134

@@ -139,6 +146,106 @@ public Task<NativeSkillCollectionIndex> GenerateSkillIndexAsync(CancellationToke
139146
};
140147
}
141148

149+
public Task<NativeSubAgentCollectionIndex> GenerateSubAgentIndexAsync(CancellationToken ct = default)
150+
{
151+
var index = new NativeSubAgentCollectionIndex
152+
{
153+
Links = SelfLinks("/manifest/subagents/index.json"),
154+
Pages = [new NativeManifestPageLink
155+
{
156+
Range = SubAgentsPageRange,
157+
Href = "/manifest/subagents/pages/all.json"
158+
}]
159+
};
160+
161+
return Task.FromResult(index);
162+
}
163+
164+
public async Task<NativeSubAgentCollectionPage?> GenerateSubAgentPageAsync(string page, CancellationToken ct = default)
165+
{
166+
if (!page.Equals(SubAgentsPageRange, StringComparison.OrdinalIgnoreCase))
167+
return null;
168+
169+
var latestVersions = await _subAgentRepository.GetAllLatestVersionsWithMetadataAsync(ct);
170+
var items = new List<NativeSubAgentPageItem>(latestVersions.Count);
171+
172+
foreach (var latest in latestVersions)
173+
{
174+
var versions = await _subAgentRepository.GetAllVersionsAsync(latest.SubAgentId, ct);
175+
if (versions.Count == 0)
176+
continue;
177+
178+
var oldest = versions[^1];
179+
items.Add(new NativeSubAgentPageItem
180+
{
181+
Name = latest.SubAgentName,
182+
LatestVersion = latest.Version,
183+
VersionRange = new NativeVersionRange
184+
{
185+
Min = oldest.Version,
186+
Max = latest.Version,
187+
Count = versions.Count
188+
},
189+
Href = $"/manifest/subagents/{latest.SubAgentName}/index.json"
190+
});
191+
}
192+
193+
return new NativeSubAgentCollectionPage
194+
{
195+
Range = SubAgentsPageRange,
196+
Items = items,
197+
Links = SelfLinks("/manifest/subagents/pages/all.json")
198+
};
199+
}
200+
201+
public async Task<NativeSubAgentIdentityIndex?> GenerateSubAgentIdentityAsync(string subAgentName, CancellationToken ct = default)
202+
{
203+
var subAgent = await _subAgentRepository.GetSubAgentByNameAsync(subAgentName, ct);
204+
if (subAgent is null)
205+
return null;
206+
207+
var versions = await _subAgentRepository.GetAllVersionsAsync(subAgent.Id, ct);
208+
if (versions.Count == 0)
209+
return null;
210+
211+
var latest = versions.FirstOrDefault(v => v.IsLatest) ?? versions[0];
212+
return new NativeSubAgentIdentityIndex
213+
{
214+
Name = subAgent.Name,
215+
LatestVersion = latest.Version,
216+
Versions = versions.Select(v => new NativeSubAgentVersionLink
217+
{
218+
Version = v.Version,
219+
PublishedAt = v.PublishedAt,
220+
Digest = Sha256Digest.Create(v.Sha256).Value,
221+
Href = $"/manifest/subagents/{subAgent.Name}/versions/{v.Version}.json"
222+
}).ToList(),
223+
Links = SelfLinks($"/manifest/subagents/{subAgent.Name}/index.json")
224+
};
225+
}
226+
227+
public async Task<NativeSubAgentVersionDetail?> GenerateSubAgentVersionAsync(string subAgentName, string version, CancellationToken ct = default)
228+
{
229+
var subAgent = await _subAgentRepository.GetSubAgentByNameAsync(subAgentName, ct);
230+
if (subAgent is null)
231+
return null;
232+
233+
var subAgentVersion = await _subAgentRepository.GetVersionAsync(subAgent.Id, version, ct);
234+
if (subAgentVersion is null)
235+
return null;
236+
237+
var baseUrl = GetBaseUrl();
238+
return new NativeSubAgentVersionDetail
239+
{
240+
Name = subAgent.Name,
241+
Version = subAgentVersion.Version,
242+
Description = subAgentVersion.Description,
243+
Url = $"{baseUrl}/subagents/{subAgent.Name}/{subAgentVersion.Version}/agent.md",
244+
Digest = Sha256Digest.Create(subAgentVersion.Sha256).Value,
245+
Links = SelfLinks($"/manifest/subagents/{subAgent.Name}/versions/{subAgentVersion.Version}.json")
246+
};
247+
}
248+
142249
private NativeSkillArtifact CreateArtifact(string skillName, SkillVersion version)
143250
{
144251
var baseUrl = GetBaseUrl();

0 commit comments

Comments
 (0)