- 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
37 lines
1.5 KiB
C#
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));
|
|
}
|