| | | 1 | | using NexusLabs.Needlr.AgentFramework.Context; |
| | | 2 | | using NexusLabs.Needlr.AgentFramework.Workspace; |
| | | 3 | | |
| | | 4 | | namespace NexusLabs.Needlr.AgentFramework.Testing; |
| | | 5 | | |
| | | 6 | | /// <summary> |
| | | 7 | | /// Mutable builder used by |
| | | 8 | | /// <see cref="ToolInvocationRunner.WithExecutionContext(Action{AgentExecutionContextBuilder})"/> |
| | | 9 | | /// to configure the <see cref="IAgentExecutionContext"/> a tool sees during invocation. |
| | | 10 | | /// </summary> |
| | | 11 | | /// <remarks> |
| | | 12 | | /// <para> |
| | | 13 | | /// A fresh <see cref="AgentExecutionContextBuilder"/> instance is created per |
| | | 14 | | /// <see cref="ToolInvocationRunner.WithExecutionContext(Action{AgentExecutionContextBuilder})"/> |
| | | 15 | | /// call so that successive runner instances do not share mutable state. |
| | | 16 | | /// </para> |
| | | 17 | | /// <para> |
| | | 18 | | /// The resulting context is established via |
| | | 19 | | /// <see cref="IAgentExecutionContextAccessor.BeginScope(IAgentExecutionContext)"/> for the |
| | | 20 | | /// duration of a single <c>InvokeAsync</c> call, then disposed. |
| | | 21 | | /// </para> |
| | | 22 | | /// </remarks> |
| | | 23 | | public sealed class AgentExecutionContextBuilder |
| | | 24 | | { |
| | 6 | 25 | | private string _userId = "tool-invocation-runner"; |
| | 6 | 26 | | private string _orchestrationId = $"tool-invocation-{Guid.NewGuid():N}"; |
| | | 27 | | private IWorkspace? _workspace; |
| | | 28 | | private Dictionary<string, object>? _properties; |
| | | 29 | | |
| | | 30 | | /// <summary> |
| | | 31 | | /// Sets the <see cref="IAgentExecutionContext.UserId"/> for the invocation context. |
| | | 32 | | /// Default is <c>"tool-invocation-runner"</c>. |
| | | 33 | | /// </summary> |
| | | 34 | | public AgentExecutionContextBuilder WithUserId(string userId) |
| | | 35 | | { |
| | 2 | 36 | | ArgumentException.ThrowIfNullOrWhiteSpace(userId); |
| | 1 | 37 | | _userId = userId; |
| | 1 | 38 | | return this; |
| | | 39 | | } |
| | | 40 | | |
| | | 41 | | /// <summary> |
| | | 42 | | /// Sets the <see cref="IAgentExecutionContext.OrchestrationId"/> for the invocation context. |
| | | 43 | | /// Default is a fresh GUID-derived string per builder. |
| | | 44 | | /// </summary> |
| | | 45 | | public AgentExecutionContextBuilder WithOrchestrationId(string orchestrationId) |
| | | 46 | | { |
| | 1 | 47 | | ArgumentException.ThrowIfNullOrWhiteSpace(orchestrationId); |
| | 0 | 48 | | _orchestrationId = orchestrationId; |
| | 0 | 49 | | return this; |
| | | 50 | | } |
| | | 51 | | |
| | | 52 | | /// <summary> |
| | | 53 | | /// Creates a fresh <see cref="InMemoryWorkspace"/>, runs <paramref name="seed"/> against it, |
| | | 54 | | /// and attaches it to the execution context. This is the most common path: a per-invocation |
| | | 55 | | /// throw-away workspace pre-populated with fixture files. |
| | | 56 | | /// </summary> |
| | | 57 | | /// <param name="seed">Callback that writes seed files to the workspace.</param> |
| | | 58 | | /// <remarks> |
| | | 59 | | /// Calling this multiple times replaces the previously-attached workspace; only the last call |
| | | 60 | | /// wins. If a workspace was already supplied via the |
| | | 61 | | /// <see cref="WithWorkspace(IWorkspace)"/> overload, it is replaced. |
| | | 62 | | /// </remarks> |
| | | 63 | | public AgentExecutionContextBuilder WithWorkspace(Action<IWorkspace> seed) |
| | | 64 | | { |
| | 2 | 65 | | ArgumentNullException.ThrowIfNull(seed); |
| | 1 | 66 | | var workspace = new InMemoryWorkspace(); |
| | 1 | 67 | | seed(workspace); |
| | 1 | 68 | | _workspace = workspace; |
| | 1 | 69 | | return this; |
| | | 70 | | } |
| | | 71 | | |
| | | 72 | | /// <summary> |
| | | 73 | | /// Attaches a caller-supplied <see cref="IWorkspace"/> to the execution context. |
| | | 74 | | /// Use this when the test needs a workspace implementation other than |
| | | 75 | | /// <see cref="InMemoryWorkspace"/>, or wants to share a workspace across invocations. |
| | | 76 | | /// </summary> |
| | | 77 | | public AgentExecutionContextBuilder WithWorkspace(IWorkspace workspace) |
| | | 78 | | { |
| | 2 | 79 | | ArgumentNullException.ThrowIfNull(workspace); |
| | 1 | 80 | | _workspace = workspace; |
| | 1 | 81 | | return this; |
| | | 82 | | } |
| | | 83 | | |
| | | 84 | | /// <summary> |
| | | 85 | | /// Adds a custom property to the execution context's |
| | | 86 | | /// <see cref="IAgentExecutionContext.Properties"/> bag. Useful for tools that read tenant |
| | | 87 | | /// IDs, correlation tokens, or other ambient data via |
| | | 88 | | /// <see cref="AgentExecutionContextExtensions.GetProperty{T}(IAgentExecutionContext, string)"/>. |
| | | 89 | | /// </summary> |
| | | 90 | | public AgentExecutionContextBuilder WithProperty(string key, object value) |
| | | 91 | | { |
| | 2 | 92 | | ArgumentException.ThrowIfNullOrWhiteSpace(key); |
| | 1 | 93 | | ArgumentNullException.ThrowIfNull(value); |
| | 0 | 94 | | _properties ??= []; |
| | 0 | 95 | | _properties[key] = value; |
| | 0 | 96 | | return this; |
| | | 97 | | } |
| | | 98 | | |
| | 5 | 99 | | internal IWorkspace? Workspace => _workspace; |
| | | 100 | | |
| | | 101 | | internal IAgentExecutionContext Build() => |
| | 5 | 102 | | new AgentExecutionContext( |
| | 5 | 103 | | UserId: _userId, |
| | 5 | 104 | | OrchestrationId: _orchestrationId, |
| | 5 | 105 | | Properties: _properties, |
| | 5 | 106 | | Workspace: _workspace); |
| | | 107 | | } |