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; /// /// Die eine Stelle, an der Kerntypen zu Übertragungstypen werden. /// /// 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. /// internal static class Mapping { public static AgentDto ToDto( this AgentConfig agent, IReadOnlyDictionary 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()); /// /// Die Einstellungen eines Werkzeugs — ohne die Geheimnisse. /// /// Welcher Schlüssel eines ist, entscheidet : 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 Secrets, ob /// überhaupt einer gesetzt ist. /// public static ToolSettingsDto ToDto( string toolName, IReadOnlyDictionary settings) { var values = new Dictionary(StringComparer.OrdinalIgnoreCase); var secrets = new Dictionary(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 TeamSnapshotDto ToDto(this App.Services.TeamSnapshot snapshot) => new( snapshot.Members.Select(m => new TeamMemberDto( m.AgentId, m.DisplayName, m.Model, m.State.ToString(), m.Runs.Select(ToDto).ToList(), m.PendingApprovals, m.LastRunStatus, m.LastRunAtUtc, m.LastRunResult, m.TypicalRunMs, m.UnitId, m.IsLead)).ToList(), snapshot.RecentLinks.Select(ToDto).ToList(), snapshot.AtUtc, (snapshot.Units ?? []).Select(u => new TeamUnitDto( u.Id, u.Name, u.ParentId, u.Path, u.LeaderAgentId, u.Goal, u.Agents, u.States.ToDictionary(s => s.Key.ToString(), s => s.Value), u.PendingApprovals, u.Worst.ToString())).ToList()); public static ActiveRunDto ToDto(this Core.Activity.ActiveRun run) => new(run.RunId, run.AgentId, run.Source, run.TaskId, run.CalledBy, run.StartedAtUtc); public static AgentLinkDto ToDto(this Core.Activity.AgentLink link) => new(link.From, link.To, link.Kind, link.AtUtc); public static TaskDto ToDto(this TaskOverview overview) => overview.Task.ToDto() with { NextRunUtc = overview.NextRunUtc, LastRun = overview.LastRun is { } lauf ? new TaskLastRunDto(lauf.OccurredAt, lauf.Status, lauf.CostUsd, lauf.CostIsKnown, lauf.ResultRef) : null, Hinweis = overview.Hinweis }; public static TaskInput ToInput(this TaskInputDto input) => new( input.Title ?? "", input.Body ?? "", input.Assignee ?? "", input.WhenKind, input.WhenValue, input.TimeZone, input.Acceptance ?? "", input.Priority, input.ParentId ?? ""); public static ForecastDto ToDto(this InstanzPrognose prognose) => new( prognose.Aufgaben.Select(a => new ForecastTaskDto( a.TaskId, a.Title, a.AgentId, a.Model, a.LaeufeProTag, a.EinmaligOffen, a.KostenJeLauf, a.Grundlage.ToString(), a.Stichprobe, a.KostenProTag)).ToList(), prognose.AufgabenProTag, prognose.SonstigeProTag, prognose.SummeProTag, prognose.SummeProMonat, prognose.EinmaligOffen, prognose.OhneKostenbasis, prognose.Budgetwarnungen.Select(w => new ForecastBudgetDto(w.AgentId, w.KostenProTag, w.BudgetProTag)).ToList()); 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, task.When is { } when ? new TaskWhenDto(when.Kind.ToString().ToLowerInvariant(), when.Value, when.TimeZone) : null, BudgetMiss: task.BudgetMissCount > 0 || task.CatchUpPending || task.DeferredUntil is not null ? new TaskBudgetMissDto(task.BudgetMissCount, task.LastBudgetMissAt, task.DeferredUntil, task.CatchUpPending, task.IsCarriedOver) : null, CreatedBy: string.IsNullOrWhiteSpace(task.CreatedBy) ? null : task.CreatedBy, ParentId: string.IsNullOrWhiteSpace(task.ParentId) ? null : task.ParentId); /// #684: The board of a task, for a list the human sees whole. public static TaskDto WithBoard(this TaskDto dto, TaskItem task, Core.Organisation.OrgChart org) { if (!org.HasUnits || Core.Tasks.TaskBoard.UnitOf(task, org) is not { } unit) return dto; return dto with { Board = unit, BoardPath = org.PathOf(unit) }; } /// /// Ein wartender Aufruf samt seiner eingefrorenen Argumente. /// /// Die Argumente gehen roh 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. /// public static StagedCallDto ToDto(this StagedCall call, int bisherFreigegeben = 0, bool dauerfreigabenAktiv = false) { var erlaubbar = dauerfreigabenAktiv && StagingService.IstDauerhaftErlaubbar(call); return new( call.Id, call.RunId, call.AgentId, call.Tool, call.Action, call.Proposal, call.Status.ToString(), call.ArgumentsJson, call.CreatedAt, bisherFreigegeben, erlaubbar, erlaubbar && bisherFreigegeben >= StagingService.VorschlagAb); } public static StandingApprovalDto ToDto(this Dauerfreigabe d) => new(d.Id, d.AgentId, d.Tool, d.Operation, d.ErteiltVon, d.ErteiltAm, d.AnlassId, d.WiderrufenAm, d.WiderrufenVon); 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); }