clawdforge/clients/csharp/src/Clawdforge/Models/TurnResult.cs
Kayos 692b48a6b2 clients/csharp: v0.2 multi-turn Session API
- Session implements IAsyncDisposable; await using is the canonical form
- Interlocked.CompareExchange idempotency on CloseAsync (rollback on transient)
- ForgeClient.CreateSessionAsync / ListSessionsAsync / GetSessionAsync
- TurnResult.Text() helper, records throughout
- Session.ToString redacts internal _client (no bearer leak)
- SessionTests.cs: 12 tests covering await-using/idempotency/rollback/exception-still-closes/list/state/cross-token-404/redaction/regression
- README "Multi-turn / Sessions (v0.2)" section
- csproj bumped to 0.2.0

v0.1 surface unchanged.

Spec: memory/spec-clawdforge-v0.2.md
Server core: 940861f
2026-04-29 06:59:45 -07:00

37 lines
1.5 KiB
C#

using System.Text.Json.Serialization;
namespace Clawdforge.Models;
/// <summary>
/// Successful response body from <c>POST /sessions/{id}/turn</c>.
/// </summary>
/// <remarks>
/// <para>
/// v0.2 returns a turn's events as a complete batch when the turn finishes
/// — no server-sent events / streaming. Use <see cref="Text"/> to flatten
/// the text portion of the reply, or iterate <see cref="Events"/> directly
/// to inspect tool calls / thinking blocks.
/// </para>
/// <para>
/// Failure responses (HTTP 4xx / 5xx) are surfaced via
/// <see cref="Exceptions.ForgeApiException"/> rather than this type.
/// </para>
/// </remarks>
public sealed record TurnResult(
[property: JsonPropertyName("ok")] bool Ok,
[property: JsonPropertyName("session_id")] string SessionId,
[property: JsonPropertyName("turn_index")] int TurnIndex,
[property: JsonPropertyName("events")] IReadOnlyList<TurnEvent> Events,
[property: JsonPropertyName("stop_reason")] string StopReason,
[property: JsonPropertyName("duration_ms")] long DurationMs
)
{
/// <summary>
/// Concatenate the <see cref="TurnEvent.Content"/> of every
/// <c>"text"</c> event in <see cref="Events"/> into a single string.
/// Non-text events (thinking, tool calls) are skipped. Returns the
/// empty string when no text events are present.
/// </summary>
public string Text()
=> string.Concat(Events.Where(e => e.Type == "text").Select(e => e.Content ?? string.Empty));
}