| | | 1 | | namespace NexusLabs.Needlr.AgentFramework.Context; |
| | | 2 | | |
| | | 3 | | /// <summary> |
| | | 4 | | /// Extension methods for <see cref="IAgentExecutionContextAccessor"/> and |
| | | 5 | | /// <see cref="IAgentExecutionContext"/>. |
| | | 6 | | /// </summary> |
| | | 7 | | public static class AgentExecutionContextExtensions |
| | | 8 | | { |
| | | 9 | | /// <summary> |
| | | 10 | | /// Gets the workspace from the context, or <see langword="null"/> if none is set. |
| | | 11 | | /// Works with both <see cref="AgentExecutionContext"/> (which stores the workspace |
| | | 12 | | /// in Properties automatically) and custom implementations that put an |
| | | 13 | | /// <see cref="Workspace.IWorkspace"/> in the property bag under the type key. |
| | | 14 | | /// </summary> |
| | | 15 | | public static Workspace.IWorkspace? GetWorkspace(this IAgentExecutionContext context) |
| | | 16 | | { |
| | 6 | 17 | | ArgumentNullException.ThrowIfNull(context); |
| | 6 | 18 | | return context.GetProperty<Workspace.IWorkspace>(); |
| | | 19 | | } |
| | | 20 | | |
| | | 21 | | /// <summary> |
| | | 22 | | /// Gets the workspace from the context, throwing if none is set. Convenience for |
| | | 23 | | /// tool implementations that require a workspace to operate. |
| | | 24 | | /// </summary> |
| | | 25 | | /// <exception cref="InvalidOperationException">No workspace is available in the current context.</exception> |
| | | 26 | | public static Workspace.IWorkspace GetRequiredWorkspace(this IAgentExecutionContext context) |
| | | 27 | | { |
| | 3 | 28 | | ArgumentNullException.ThrowIfNull(context); |
| | 3 | 29 | | return context.GetWorkspace() ?? throw new InvalidOperationException( |
| | 3 | 30 | | "No workspace is available in the current execution context. " + |
| | 3 | 31 | | "The orchestration layer must provide an IWorkspace when constructing the execution context."); |
| | | 32 | | } |
| | | 33 | | |
| | | 34 | | /// <summary> |
| | | 35 | | /// Gets a typed property from the context's property bag, keyed by the type's |
| | | 36 | | /// full name. Returns <see langword="null"/> if not found or if the stored value |
| | | 37 | | /// is not assignable to <typeparamref name="T"/>. |
| | | 38 | | /// </summary> |
| | | 39 | | /// <remarks> |
| | | 40 | | /// <para> |
| | | 41 | | /// The default key (<c>typeof(T).FullName</c>) works well when each property type |
| | | 42 | | /// is unique within a context — the common case for domain records like |
| | | 43 | | /// <c>ArticleAssignment</c>, <c>SeoReport</c>, etc. |
| | | 44 | | /// </para> |
| | | 45 | | /// <para> |
| | | 46 | | /// <see cref="IAgentExecutionContext.Properties"/> is read-only by design. |
| | | 47 | | /// Consumers that need to store typed state should populate the property bag at |
| | | 48 | | /// context construction time via their own <see cref="IAgentExecutionContext"/> |
| | | 49 | | /// implementation. |
| | | 50 | | /// </para> |
| | | 51 | | /// </remarks> |
| | | 52 | | public static T? GetProperty<T>(this IAgentExecutionContext context) where T : class |
| | | 53 | | { |
| | 11 | 54 | | ArgumentNullException.ThrowIfNull(context); |
| | 11 | 55 | | return context.Properties.TryGetValue(typeof(T).FullName!, out var value) |
| | 11 | 56 | | ? value as T |
| | 11 | 57 | | : null; |
| | | 58 | | } |
| | | 59 | | |
| | | 60 | | /// <summary> |
| | | 61 | | /// Gets a typed property from the context's property bag with an explicit key. |
| | | 62 | | /// Returns <see langword="null"/> if not found or if the stored value is not |
| | | 63 | | /// assignable to <typeparamref name="T"/>. |
| | | 64 | | /// </summary> |
| | | 65 | | public static T? GetProperty<T>(this IAgentExecutionContext context, string key) where T : class |
| | | 66 | | { |
| | 3 | 67 | | ArgumentNullException.ThrowIfNull(context); |
| | 3 | 68 | | return context.Properties.TryGetValue(key, out var value) |
| | 3 | 69 | | ? value as T |
| | 3 | 70 | | : null; |
| | | 71 | | } |
| | | 72 | | |
| | | 73 | | /// <summary> |
| | | 74 | | /// Returns the current <see cref="IAgentExecutionContext"/> or throws if none is established. |
| | | 75 | | /// Tools should call this rather than checking <see cref="IAgentExecutionContextAccessor.Current"/> |
| | | 76 | | /// directly — there is no valid "anonymous" execution. |
| | | 77 | | /// </summary> |
| | | 78 | | /// <exception cref="InvalidOperationException"> |
| | | 79 | | /// Thrown when no execution context scope is active. This indicates a programming error: |
| | | 80 | | /// the orchestration layer must call <see cref="IAgentExecutionContextAccessor.BeginScope"/> |
| | | 81 | | /// before invoking tools. |
| | | 82 | | /// </exception> |
| | | 83 | | public static IAgentExecutionContext GetRequired(this IAgentExecutionContextAccessor accessor) |
| | | 84 | | { |
| | 3 | 85 | | ArgumentNullException.ThrowIfNull(accessor); |
| | | 86 | | |
| | 2 | 87 | | return accessor.Current ?? throw new InvalidOperationException( |
| | 2 | 88 | | "Tool invoked outside an agent execution scope. " + |
| | 2 | 89 | | "The orchestration layer must call IAgentExecutionContextAccessor.BeginScope(context) before invoking tools. |
| | | 90 | | } |
| | | 91 | | } |