- ClawdDotNet.Web: Kestrel im Prozess der Anwendung, Token-Absicherung (TokenGate mit FixedTimeEquals), Endpunkte unter /api, Ereignisstrom unter /events (Server-Sent Events) und fortlaufendes Log-Streaming. - Weboberflaeche (wwwroot/): Vollstaendiges Single-Page-Frontend mit nativem HTML5, CSS3 und ES-Modulen ohne Buildschritt (strikt textContent, #-Routing, sofortiger Login-Sprung bei fehlendem Token, Light/Dark-Theme, lokale Barlow-Schriften). - ClawdDotNet.App: Fachlogik aus den Avalonia-ViewModels in Dienste herausgeloest (AgentAdministration, JobAdministration, ServiceAdministration, RunCoordinator) und ueber InstanceChange synchronisiert. - Tests: ClawdDotNet.Web.Tests und ClawdDotNet.App.Tests hinzugefuegt, alle 684 Tests erfolgreich.
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
using ClawdDotNet.App.Services;
|
||||
using ClawdDotNet.Core.Accounting;
|
||||
using ClawdDotNet.Core.Config;
|
||||
using ClawdDotNet.Core.Engine;
|
||||
using ClawdDotNet.Core.Security;
|
||||
using ClawdDotNet.Core.Staging;
|
||||
using ClawdDotNet.Core.Tasks;
|
||||
|
||||
namespace ClawdDotNet.Web.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Die eine Stelle, an der Kerntypen zu Übertragungstypen werden.
|
||||
///
|
||||
/// <para>Eine Stelle, weil die Maskierung von Zugangsdaten hier hängt. Verstreut über
|
||||
/// zehn Endpunkte wäre die Frage „kommt hier ein Passwort heraus?" nicht mehr zu
|
||||
/// beantworten, sondern nur noch zu hoffen.</para>
|
||||
/// </summary>
|
||||
internal static class Mapping
|
||||
{
|
||||
public static AgentDto ToDto(
|
||||
this AgentConfig agent,
|
||||
IReadOnlyDictionary<string, string> toolDescriptions,
|
||||
bool isRunning)
|
||||
=> new(
|
||||
agent.AgentId,
|
||||
agent.DisplayName,
|
||||
agent.Model,
|
||||
agent.Description,
|
||||
agent.PromptCaching,
|
||||
agent.MaxToolResultChars,
|
||||
agent.LoopGuard.MaxSteps,
|
||||
agent.LoopGuard.MaxContextTokens,
|
||||
agent.LoopGuard.TimeoutSeconds,
|
||||
agent.Budget.DailyCostUsd,
|
||||
agent.Budget.DailyTokens,
|
||||
isRunning,
|
||||
AgentAdministration.KnownTools
|
||||
.Select(name => new AgentToolDto(
|
||||
name,
|
||||
toolDescriptions.GetValueOrDefault(name) ?? "",
|
||||
agent.Tools.ContainsKey(name)))
|
||||
.ToList());
|
||||
|
||||
/// <summary>
|
||||
/// Die Einstellungen eines Werkzeugs — <b>ohne die Geheimnisse</b>.
|
||||
///
|
||||
/// <para>Welcher Schlüssel eines ist, entscheidet <see cref="ConfigSecrets"/>: der
|
||||
/// Kern weiß es bereits, und eine zweite Liste hier wäre eine, die beim nächsten
|
||||
/// neuen Werkzeug vergessen wird. Statt des Werts steht in <c>Secrets</c>, ob
|
||||
/// überhaupt einer gesetzt ist.</para>
|
||||
/// </summary>
|
||||
public static ToolSettingsDto ToDto(
|
||||
string toolName, IReadOnlyDictionary<string, object?> settings)
|
||||
{
|
||||
var values = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
|
||||
var secrets = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var (key, value) in settings)
|
||||
{
|
||||
if (ConfigSecrets.IsSecretKey(key))
|
||||
secrets[key] = value is not null && value is not string { Length: 0 };
|
||||
else
|
||||
values[key] = value;
|
||||
}
|
||||
|
||||
return new ToolSettingsDto(toolName, values, secrets);
|
||||
}
|
||||
|
||||
public static ChatEntryDto ToDto(this ChatEntry entry)
|
||||
=> new(entry.Role, entry.Content, entry.Timestamp, entry.Source);
|
||||
|
||||
public static JobDto ToDto(this JobSummary job, DateTime? lastRun)
|
||||
=> new(
|
||||
job.Kind == JobKind.ToolJob ? "toolJob" : "agentWakeup",
|
||||
job.JobId,
|
||||
job.AgentId,
|
||||
job.AgentName,
|
||||
job.ToolName,
|
||||
job.Cron,
|
||||
job.Message,
|
||||
job.RunOnStart,
|
||||
job.NextRunUtc,
|
||||
lastRun,
|
||||
job.CronValid,
|
||||
job.Enabled);
|
||||
|
||||
public static JobDefinition ToDefinition(this JobInputDto input)
|
||||
=> new(
|
||||
input.AgentId,
|
||||
string.Equals(input.Kind, "toolJob", StringComparison.OrdinalIgnoreCase)
|
||||
? JobKind.ToolJob
|
||||
: JobKind.AgentWakeup,
|
||||
input.ToolName,
|
||||
input.JobTypeId,
|
||||
input.Cron,
|
||||
input.TaskMessage,
|
||||
input.RunOnStart);
|
||||
|
||||
public static ServiceDto ToDto(this ServiceConfig service)
|
||||
=> new(
|
||||
service.ServiceId,
|
||||
service.Name,
|
||||
service.Type,
|
||||
service.Port,
|
||||
service.Enabled,
|
||||
service.BuiltIn,
|
||||
service.Description);
|
||||
|
||||
public static TaskDto ToDto(this TaskItem task)
|
||||
=> new(
|
||||
task.Id,
|
||||
task.Title,
|
||||
task.Status.ToString(),
|
||||
task.Type.ToString(),
|
||||
task.Priority,
|
||||
task.Assignee,
|
||||
task.RequireApproval,
|
||||
task.Acceptance,
|
||||
task.BlockedBy,
|
||||
task.Body,
|
||||
task.CreatedAt,
|
||||
task.UpdatedAt);
|
||||
|
||||
/// <summary>
|
||||
/// Ein wartender Aufruf samt seiner eingefrorenen Argumente.
|
||||
///
|
||||
/// <para>Die Argumente gehen <b>roh</b> heraus, und das ist der ganze Sinn der
|
||||
/// Freigabe: Wer entscheidet, muss sehen, was genau ausgeführt würde — nicht eine
|
||||
/// Zusammenfassung davon. Sie sind zugleich Fremdtext aus einem Modell; die Anzeige
|
||||
/// setzt sie als Text ein, nie als HTML.</para>
|
||||
/// </summary>
|
||||
public static StagedCallDto ToDto(this StagedCall call)
|
||||
=> new(
|
||||
call.Id,
|
||||
call.RunId,
|
||||
call.AgentId,
|
||||
call.Tool,
|
||||
call.Action,
|
||||
call.Proposal,
|
||||
call.Status.ToString(),
|
||||
call.ArgumentsJson);
|
||||
|
||||
public static UsageDto ToDto(this RunUsage usage)
|
||||
=> new(
|
||||
usage.Id,
|
||||
usage.AgentId,
|
||||
usage.Model,
|
||||
usage.PromptTokens,
|
||||
usage.CompletionTokens,
|
||||
usage.CachedTokens,
|
||||
usage.CostUsd,
|
||||
usage.CostIsKnown,
|
||||
usage.Status,
|
||||
usage.StepCount,
|
||||
usage.OccurredAt);
|
||||
}
|
||||
Reference in New Issue
Block a user