Forge
csharp3407750f
1using AgentForge.Abstractions;
2
3using AgentForge;
4
5using AgentForge.Configuration;
6
7using LibGit2Sharp;
8
9using AgentForge.Data;
10
11using AgentForge.Endpoints;
12
13using AgentForge.Middleware;
14
15using AgentForge.Options;
16
17using AgentForge.Plugins;
18
19using AgentForge.Services;
20
21using Microsoft.EntityFrameworkCore;
22
23using Microsoft.Extensions.Options;
24
25using OutWit.Database.EntityFramework.Extensions;
26
27
28
29// Shared Docker volume: bare repos may be owned by git-ssh user, not the API process.
30GlobalSettings.SetOwnerValidation(false);
31
32var builder = WebApplication.CreateBuilder(args);
33
34ForgeEnvironment.Configure(builder.Configuration);
35
36
37
38builder.Services.Configure<ForgeOptions>(builder.Configuration.GetSection(ForgeOptions.SectionName));
39
40builder.Services.PostConfigure<ForgeOptions>(ForgeEnvironment.ApplyEnvironmentOverrides);
41
42
43
44builder.Services.AddDbContext<ForgeDbContext>((sp, options) =>
45
46{
47
48 var forgeOptions = sp.GetRequiredService<IOptions<ForgeOptions>>().Value;
49
50 var directory = Path.GetDirectoryName(forgeOptions.DatabasePath);
51
52 if (!string.IsNullOrEmpty(directory))
53
54 Directory.CreateDirectory(directory);
55
56
57
58 options.UseWitDb($"Data Source={forgeOptions.DatabasePath}");
59
60});
61
62
63
64builder.Services.AddScoped<ForgeRepository>();
65
66builder.Services.AddSingleton<GitRepoService>();
67builder.Services.AddSingleton(_ =>
68 ForgePluginLoader.LoadManifest(builder.Environment.ContentRootPath, builder.Configuration));
69builder.Services.AddSingleton<ForgeImportVisibilityService>();
70
71builder.Services.AddSingleton<ForgeUrls>();
72builder.Services.AddSingleton<IForgePluginHost, ForgePluginHostAdapter>();
73
74builder.Services.AddScoped<ForgeAuthService>();
75
76builder.Services.AddScoped<ForgeDeviceAuthService>();
77
78builder.Services.AddScoped<ForgeGitHubOAuthService>();
79
80builder.Services.AddScoped<ForgeOAuthProviderRegistry>();
81
82builder.Services.AddScoped<ForgeOAuthLoginService>();
83
84builder.Services.AddScoped<ForgeOAuthSignInCoordinator>();
85
86builder.Services.AddHttpClient();
87
88builder.Services.AddScoped<ForgeAuthorizationService>();
89builder.Services.AddScoped<ForgeRepoAccessService>();
90builder.Services.AddScoped<ForgeRepositoryImporterRegistry>();
91builder.Services.AddScoped<ForgeOrgImporterRegistry>();
92
93builder.Services.AddHttpClient(nameof(CiWebhookService));
94
95builder.Services.AddSingleton<CiWebhookService>();
96
97var pluginRuntime = ForgePluginRuntime.Load(builder);
98builder.Services.AddSingleton(pluginRuntime);
99builder.Services.AddSingleton<IForgeViewContributorCatalog>(pluginRuntime);
100builder.Services.AddSingleton<IForgeRepoTabBodyCatalog>(pluginRuntime);
101builder.Services.AddSingleton<IForgeOAuthLoginContributorCatalog>(pluginRuntime);
102builder.Services.AddSingleton<ForgePluginReloadService>();
103builder.Services.AddSingleton<IForgeHostLifetime, ForgeHostLifetime>();
104builder.Services.AddHostedService<ForgePluginWatchHostedService>();
105builder.Services.AddSingleton<ForgeCapabilitiesService>();
106builder.Services.AddSingleton<ForgeCommandExecutor>();
107builder.Services.AddSingleton(_ => ForgeMcpKernelDispatch.Build());
108builder.Services.AddSingleton<IForgeMcpHttpClientSource, ProductionForgeMcpHttpClientSource>();
109builder.Services.AddSingleton<ForgeMcpToolExecutor>();
110
111
112
113var app = builder.Build();
114
115app.UseMiddleware<ForgeAuthMiddleware>();
116
117app.UseExceptionHandler(exceptionHandler =>
118
119{
120
121 exceptionHandler.Run(async context =>
122
123 {
124
125 var feature = context.Features.Get<Microsoft.AspNetCore.Diagnostics.IExceptionHandlerFeature>();
126
127 if (feature?.Error is ForgeApiException apiError)
128
129 {
130
131 context.Response.StatusCode = apiError.StatusCode;
132
133 await context.Response.WriteAsJsonAsync(new { detail = apiError.Message });
134
135 return;
136
137 }
138
139
140
141 context.Response.StatusCode = StatusCodes.Status500InternalServerError;
142
143 await context.Response.WriteAsJsonAsync(new { detail = "Internal server error." });
144
145 });
146
147});
148
149
150
151using (var scope = app.Services.CreateScope())
152
153{
154
155 var db = scope.ServiceProvider.GetRequiredService<ForgeDbContext>();
156
157 ForgeDatabaseBootstrap.ApplyKernel(db);
158 ForgeOrgBootstrap.EnsureDefaultOrg(db, scope.ServiceProvider.GetRequiredService<IOptions<ForgeOptions>>().Value);
159
160 var migrationContext = new ForgeMigrationContext(db);
161
162 ForgePluginLoader.ApplyMigrations(pluginRuntime.Registry.Plugins, migrationContext);
163
164}
165
166
167
168app.MapGet("/openapi/v1.json", (ForgeCapabilitiesService capabilities) =>
169{
170 var caps = capabilities.Build();
171 return Results.Json(new
172 {
173 openapi = "3.1.0",
174 info = new { title = "Agent Forge API", version = "0.3.0" },
175 servers = new[] { new { url = "/api/v1" } },
176 paths = new Dictionary<string, object>
177 {
178 ["/capabilities"] = new { get = new { summary = "Forge bundle, plugins, MCP tool catalog, slash commands" } },
179 ["/commands/execute"] = new { post = new { summary = "Execute DOI slash command (redirect, JSON, or MCP)" } },
180 ["/mcp/invoke"] = new { post = new { summary = "Execute MCP tool on host" } },
181 ["/repos"] = new { get = new { summary = "List repositories" }, post = new { summary = "Create repository" } },
182 },
183 extensions = new { forgeBundle = caps.Bundle, mcpTools = caps.McpTools },
184 });
185});
186
187app.MapGet("/", () => Results.Ok(new
188
189{
190
191 service = "agent-forge",
192 version = "0.3.0",
193 stack = "dotnet",
194 store = "witdb",
195 docs = "/openapi/v1.json",
196 view = "/view",
197 skill = "https://github.com/AI-Guiders/agent-forge/blob/main/docs/skill.md",
198}));
199
200app.MapGet("/health", (ForgePluginRuntime plugins, ForgeCapabilitiesService capabilities) =>
201{
202 var caps = capabilities.Build();
203 return Results.Ok(new
204 {
205 status = "ok",
206 bundle = caps.Bundle,
207 pluginDiscovery = plugins.Registry.Discovery.ToString().ToLowerInvariant(),
208 plugins = caps.Plugins.Select(p => new
209 {
210 id = p.Id,
211 tier = p.Tier,
212 commandCount = p.CommandCount,
213 }).ToList(),
214 features = caps.Features,
215 commandCount = caps.Commands.Count,
216 commandsBySurface = plugins.CommandCatalog.CountBySurface(),
217 });
218});
219
220
221
222var api = app.MapGroup("/api/v1");
223
224api.MapForgeApi();
225
226ForgePluginLoader.MapEndpoints(pluginRuntime.Registry.Plugins, app);
227
228app.Run();
229
230
231
232public partial class Program;
233
234
235
View only · write via MCP/CIDE