Files
ClawdDotNet/src/ClawdDotNet.Web/Contracts/Mapping.cs
T
RichardandClaude Opus 5.5 183e922278 Fuellarbeit: zeitlich flexible Arbeit fuellt ungenutztes Budget (#717, Stufe 1)
Eine einmalige Aufgabe mit fill_by (spaetestens bis) statt Termin laeuft,
wenn der Tag Budget uebrig laesst: nach regulaerer und nachgeholter Arbeit,
nur wenn nichts vom Agenten wartet, bevorzugt ab 18 Uhr, und nur wenn ihre
eigene Schaetzung in den Spielraum aus #716 passt. Kurz vor der Frist wird
einmal ein Mensch gefragt; nach der Frist laeuft sie als regulaere Arbeit.

Ehrliche Schaetzung (TaskCostEstimator): eigene Laeufe, dann Geschwister
unter derselben Elternaufgabe, dann die Angabe des Autors (als Schaetzung
gekennzeichnet), zuletzt das Mittel des Agenten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 15:34:15 +02:00

223 lines
9.0 KiB
C#

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,
Func<string, Core.Tools.IAgentTool?>? tools = null)
=> 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),
tools?.Invoke(name)?.ReadsForeignContent == true))
.ToList(),
tools is null ? [] : Core.Organisation.ReportingLine.TrustOf(agent, tools).Sources,
agent.TreatAsForeignReader);
/// <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 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 ?? "",
input.FillBy,
input.EstimateUsd);
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,
OriginRunId: string.IsNullOrWhiteSpace(task.OriginRunId) ? null : task.OriginRunId,
OriginTaskId: string.IsNullOrWhiteSpace(task.OriginTaskId) ? null : task.OriginTaskId,
FillBy: task.IsFillWork ? task.FillBy : null,
EstimateUsd: task.EstimateUsd,
FillAlertedAt: task.FillAlertedAt);
/// <summary>#684: The board of a task, for a list the human sees whole.</summary>
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) };
}
/// <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, 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);
}