diff --git a/config/example.yaml b/config/example.yaml
index cab7f2fa..6ed935dd 100644
--- a/config/example.yaml
+++ b/config/example.yaml
@@ -194,9 +194,7 @@ tools:
- { id: search, kind: builtin, uses: web.search }
# kind: builtin, a plain function. The model calls it when a file in the conversation's workspace is a
- # deliverable: the file is copied into providers.blobs (see 8. providers), owned by the conversation,
- # and the person gets a link. Files not published are lost when the conversation ends. Needs
- # providers.blobs and options.UseWorkspace(...) on the host; missing either fails the boot.
+ # deliverable: the file is copied into providers.blobs.
- { id: publish, kind: builtin, uses: file.publish }
# kind: binding, typed form. Not used in this example. The host registered
diff --git a/src/AgentCore.Application/Blobs/BlobLink.cs b/src/AgentCore.Application/Blobs/BlobLink.cs
index a2d5237d..be7ff0e0 100644
--- a/src/AgentCore.Application/Blobs/BlobLink.cs
+++ b/src/AgentCore.Application/Blobs/BlobLink.cs
@@ -4,7 +4,7 @@ namespace AgentCore.Application.Blobs
public static class BlobLink
{
/// How long a link works.
- public static TimeSpan Lifetime { get; } = TimeSpan.FromMinutes(15);
+ public static TimeSpan Lifetime { get; } = TimeSpan.FromMinutes(5);
///
/// The Content-Disposition a link serves the blob with: inline for what a browser
diff --git a/src/AgentCore.Application/Blobs/BlobOwner.cs b/src/AgentCore.Application/Blobs/BlobOwner.cs
new file mode 100644
index 00000000..4e35bacc
--- /dev/null
+++ b/src/AgentCore.Application/Blobs/BlobOwner.cs
@@ -0,0 +1,16 @@
+namespace AgentCore.Application.Blobs
+{
+ ///
+ /// The one shape a blob owner id may take. An owner id is the first segment of a key, never a path.
+ ///
+ public static class BlobOwner
+ {
+ /// Says whether an owner id is safe to use as the first segment of a key.
+ /// The conversation id or other owner the caller wrote.
+ /// when the id is not blank and holds no slash.
+ public static bool IsSafe(string? ownerId)
+ {
+ return !string.IsNullOrWhiteSpace(ownerId) && !ownerId.Contains('/', StringComparison.Ordinal);
+ }
+ }
+}
diff --git a/src/AgentCore.Application/Blobs/FileLink.cs b/src/AgentCore.Application/Blobs/FileLink.cs
deleted file mode 100644
index ef831b6c..00000000
--- a/src/AgentCore.Application/Blobs/FileLink.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace AgentCore.Application.Blobs
-{
- /// One file a conversation's sandbox wrote and the store kept.
- /// The file's facts.
- /// Where a browser fetches it from, or when the store has no web door.
- public sealed record FileLink(BlobRef Blob, Uri? Url);
-}
diff --git a/src/AgentCore.Application/Configuration/Compilation/AgentToolCompiler.cs b/src/AgentCore.Application/Configuration/Compilation/AgentToolCompiler.cs
index 3de0e9cf..12003f32 100644
--- a/src/AgentCore.Application/Configuration/Compilation/AgentToolCompiler.cs
+++ b/src/AgentCore.Application/Configuration/Compilation/AgentToolCompiler.cs
@@ -101,7 +101,7 @@ private static void Add(
if (context.ChatClients.ResolveHostedTool(tool, model) is not { } resolved)
{
ILogger logger = context.Loggers?.CreateLogger(typeof(AgentToolCompiler)) ?? NullLogger.Instance;
-
+
string modelDescription = model is { Ref.Length: > 0 } ? $"the model '{model.Ref}'" : "this agent's default model";
Log.HostedToolDropped(logger, agentId, toolId, tool.GetType().Name, modelDescription);
diff --git a/src/AgentCore.Application/Configuration/Compilation/PolicyRow.cs b/src/AgentCore.Application/Configuration/Compilation/PolicyRow.cs
index 68cdfb2e..012fa3e8 100644
--- a/src/AgentCore.Application/Configuration/Compilation/PolicyRow.cs
+++ b/src/AgentCore.Application/Configuration/Compilation/PolicyRow.cs
@@ -38,7 +38,7 @@ internal override EntryBuild BuildEntry(
StageConfiguration stage = policy.Stages[index];
string stagePointer = ConfigurationError.AppendPointer(
ConfigurationError.AppendPointer(policyPointer, "stages"), index);
-
+
if (stage.Agent is not { } agentId)
{
stages[stage.Id] = NoAgentId;
diff --git a/src/AgentCore.Application/Conversation/Conversations.cs b/src/AgentCore.Application/Conversation/Conversations.cs
index a04ec4ae..f66db0f9 100644
--- a/src/AgentCore.Application/Conversation/Conversations.cs
+++ b/src/AgentCore.Application/Conversation/Conversations.cs
@@ -45,7 +45,7 @@ public Conversations(IConversationStore conversations, IBlobStore? blobs)
IReadOnlyList messages = await Store.ReadWindowAsync(conversationId, window, cancellationToken).ConfigureAwait(false);
- IReadOnlyList files = await LinkFilesAsync(conversationId, messages.Select(message => message.Content), cancellationToken).ConfigureAwait(false);
+ IReadOnlyList files = KeptFiles(conversationId, messages.Select(message => message.Content));
int turns = messages.Select(message => message.TurnIndex).Distinct().Count();
@@ -55,15 +55,11 @@ public Conversations(IConversationStore conversations, IBlobStore? blobs)
};
}
- /// Links every published file the messages carry and the store kept, each name once, in first-seen order.
+ /// Lists every published file the messages carry and the store kept, each name once, in first-seen order.
/// The conversation that owns the files.
/// The messages to read the references off: the stored transcript, or one turn's updates.
- /// Cancels the signing.
- /// One link per kept file.
- public async Task> LinkFilesAsync(
- string conversationId,
- IEnumerable messages,
- CancellationToken cancellationToken = default)
+ /// The facts of each kept file. The last mention of a name wins its place's facts.
+ internal IReadOnlyList KeptFiles(string conversationId, IEnumerable messages)
{
ArgumentException.ThrowIfNullOrWhiteSpace(conversationId);
ArgumentNullException.ThrowIfNull(messages);
@@ -95,15 +91,22 @@ public async Task> LinkFilesAsync(
}
}
- List links = [];
+ return kept;
+ }
- foreach (BlobRef blob in kept)
+ ///
+ public async ValueTask LinkFileAsync(string conversationId, string name, CancellationToken cancellationToken = default)
+ {
+ if (_blobs is null || !BlobOwner.IsSafe(conversationId) || !BlobName.IsSafe(name))
{
- Uri? url = await _blobs.LinkAsync(blob, BlobLink.Lifetime, cancellationToken).ConfigureAwait(false);
- links.Add(new FileLink(blob, url));
+ return null;
}
- return links;
+ BlobRef? blob = await _blobs.StatAsync(conversationId, name, cancellationToken).ConfigureAwait(false);
+
+ return blob is null
+ ? null
+ : await _blobs.LinkAsync(blob, BlobLink.Lifetime, cancellationToken).ConfigureAwait(false);
}
///
diff --git a/src/AgentCore.Application/Conversation/StoredConversation.cs b/src/AgentCore.Application/Conversation/StoredConversation.cs
index 4a908714..062326ca 100644
--- a/src/AgentCore.Application/Conversation/StoredConversation.cs
+++ b/src/AgentCore.Application/Conversation/StoredConversation.cs
@@ -1,4 +1,5 @@
using AgentCore.Application.Blobs;
+using AgentCore.Application.Ports;
using AgentCore.Application.Transcript;
namespace AgentCore.Application.Conversation
@@ -7,13 +8,13 @@ namespace AgentCore.Application.Conversation
/// The row.
/// The stored messages, oldest first: every one, or one window's worth.
///
- /// One link per file the messages name and the blob store kept, each name once, in first-seen order.
- /// The links expire; read them out at once rather than keeping this record around.
+ /// The facts of each file the messages name and the blob store kept, each name once, in first-seen order.
+ /// A link to the bytes comes from when the person asks for one.
///
public sealed record StoredConversation(
ConversationRecord Conversation,
IReadOnlyList Messages,
- IReadOnlyList Files)
+ IReadOnlyList Files)
{
///
/// Gets the turn to read the next older window before, or when
diff --git a/src/AgentCore.Application/Hooks/AgentHook.cs b/src/AgentCore.Application/Hooks/AgentHook.cs
index d55e754e..0462e1cd 100644
--- a/src/AgentCore.Application/Hooks/AgentHook.cs
+++ b/src/AgentCore.Application/Hooks/AgentHook.cs
@@ -12,253 +12,379 @@ public abstract class AgentHook
/// The facts and the verbs of this gate.
/// Cancelled at the gate's deadline.
/// A task that completes when the hook decided.
- public virtual ValueTask BeforeCallAsync(CallGate gate, CancellationToken cancellationToken) => default;
+ public virtual ValueTask BeforeCallAsync(CallGate gate, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Every AgentCore route, HTTP or call, needs an entry. Decide with or .
/// The facts and the verbs of this gate.
/// Cancelled at the gate's deadline.
/// A task that completes when the hook decided.
- public virtual ValueTask BeforeEntryAsync(EntryGate gate, CancellationToken cancellationToken) => default;
+ public virtual ValueTask BeforeEntryAsync(EntryGate gate, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// The user's message arrives, before moderation and the model.
/// The facts and the verbs of this gate.
/// Cancelled at the gate's deadline.
/// A task that completes when the hook decided.
- public virtual ValueTask BeforeTurnAsync(TurnGate gate, CancellationToken cancellationToken) => default;
+ public virtual ValueTask BeforeTurnAsync(TurnGate gate, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Once per run, when the context is built. A graph participant or an agent-as-tool child is a run of its own, so it fires again with set.
/// The facts and the verbs of this gate.
/// Cancelled at the gate's deadline.
/// A task that completes when the hook decided.
- public virtual ValueTask BeforeRunAsync(RunGate gate, CancellationToken cancellationToken) => default;
+ public virtual ValueTask BeforeRunAsync(RunGate gate, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// AgentCore is about to compact.
/// The facts and the verbs of this gate.
/// Cancelled at the gate's deadline.
/// A task that completes when the hook decided.
- public virtual ValueTask BeforeCompactionAsync(CompactionGate gate, CancellationToken cancellationToken) => default;
+ public virtual ValueTask BeforeCompactionAsync(CompactionGate gate, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Before each model round trip.
/// The facts and the verbs of this gate.
/// Cancelled at the gate's deadline.
/// A task that completes when the hook decided.
- public virtual ValueTask BeforeModelAsync(ModelGate gate, CancellationToken cancellationToken) => default;
+ public virtual ValueTask BeforeModelAsync(ModelGate gate, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// After each round trip, before tools run or text leaves. Overriding this method buffers every streamed model round of every turn before any of it reaches the caller.
/// The facts and the verbs of this gate.
/// Cancelled at the gate's deadline.
/// A task that completes when the hook decided.
- public virtual ValueTask AfterModelAsync(ModelResultGate gate, CancellationToken cancellationToken) => default;
+ public virtual ValueTask AfterModelAsync(ModelResultGate gate, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// A round trip throws.
/// The facts and the verbs of this gate.
/// Cancelled at the gate's deadline.
/// A task that completes when the hook decided.
- public virtual ValueTask AfterModelFailedAsync(ModelFailureGate gate, CancellationToken cancellationToken) => default;
+ public virtual ValueTask AfterModelFailedAsync(ModelFailureGate gate, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// A tool that needs approval is called.
/// The facts and the verbs of this gate.
/// Cancelled at the gate's deadline.
/// A task that completes when the hook decided.
- public virtual ValueTask BeforeToolApprovalAsync(ApprovalGate gate, CancellationToken cancellationToken) => default;
+ public virtual ValueTask BeforeToolApprovalAsync(ApprovalGate gate, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Before each tool call.
/// The facts and the verbs of this gate.
/// Cancelled at the gate's deadline.
/// A task that completes when the hook decided.
- public virtual ValueTask BeforeToolAsync(ToolGate gate, CancellationToken cancellationToken) => default;
+ public virtual ValueTask BeforeToolAsync(ToolGate gate, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// After a tool returns.
/// The facts and the verbs of this gate.
/// Cancelled at the gate's deadline.
/// A task that completes when the hook decided.
- public virtual ValueTask AfterToolAsync(ToolResultGate gate, CancellationToken cancellationToken) => default;
+ public virtual ValueTask AfterToolAsync(ToolResultGate gate, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// A tool throws.
/// The facts and the verbs of this gate.
/// Cancelled at the gate's deadline.
/// A task that completes when the hook decided.
- public virtual ValueTask AfterToolFailedAsync(ToolFailureGate gate, CancellationToken cancellationToken) => default;
+ public virtual ValueTask AfterToolFailedAsync(ToolFailureGate gate, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// The run has a reply, before the turn is sealed.
/// The facts and the verbs of this gate.
/// Cancelled at the gate's deadline.
/// A task that completes when the hook decided.
- public virtual ValueTask AfterRunAsync(RunEndGate gate, CancellationToken cancellationToken) => default;
+ public virtual ValueTask AfterRunAsync(RunEndGate gate, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Boot finished.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnHostStartedAsync(HostStarted notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnHostStartedAsync(HostStarted notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Shutdown begins.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnHostStoppingAsync(HostStopping notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnHostStoppingAsync(HostStopping notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// The hourly retention sweep ran.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnRetentionSweptAsync(RetentionSwept notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnRetentionSweptAsync(RetentionSwept notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// A call is accepted and the session is open.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnCallStartedAsync(CallStarted notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnCallStartedAsync(CallStarted notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// A spoken line closes.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnLineSpokenAsync(LineSpoken notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnLineSpokenAsync(LineSpoken notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// After the store opens.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnConversationStartedAsync(ConversationStarted notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnConversationStartedAsync(ConversationStarted notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Idle unload or close; the conversation lives on in the store.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnConversationUnloadedAsync(ConversationUnloaded notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnConversationUnloadedAsync(ConversationUnloaded notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// The conversation ends. Once per conversation.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnConversationEndedAsync(ConversationEnded notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnConversationEndedAsync(ConversationEnded notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// The turn is admitted and its agent chosen.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnTurnStartedAsync(TurnStarted notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnTurnStartedAsync(TurnStarted notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Any refusal.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnTurnRefusedAsync(TurnRefused notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnTurnRefusedAsync(TurnRefused notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Edit-and-resend withdrew turns.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnTurnSupersededAsync(TurnSuperseded notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnTurnSupersededAsync(TurnSuperseded notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Moderation decided.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnInputModeratedAsync(InputModerated notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnInputModeratedAsync(InputModerated notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// A knowledge search finished.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnKnowledgeSearchedAsync(KnowledgeSearched notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnKnowledgeSearchedAsync(KnowledgeSearched notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// A skill was pinned or loaded.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnSkillLoadedAsync(SkillLoaded notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnSkillLoadedAsync(SkillLoaded notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Compaction finished or failed.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnCompactedAsync(Compacted notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnCompactedAsync(Compacted notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Each output update that leaves the turn. Overriding this method makes AgentCore raise it for every update.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnReplyUpdatedAsync(ReplyUpdated notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnReplyUpdatedAsync(ReplyUpdated notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Each model round trip ended.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnModelCalledAsync(ModelCalled notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnModelCalledAsync(ModelCalled notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Each tool call ended. One per call.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnToolCalledAsync(ToolCalled notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnToolCalledAsync(ToolCalled notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Approval asked or answered.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnApprovalChangedAsync(ApprovalChanged notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnApprovalChangedAsync(ApprovalChanged notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// file.publish stored a file.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnFilePublishedAsync(FilePublished notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnFilePublishedAsync(FilePublished notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// An agent-as-tool run began.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnSubagentStartedAsync(SubagentStarted notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnSubagentStartedAsync(SubagentStarted notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// An agent-as-tool run ended.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnSubagentEndedAsync(SubagentEnded notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnSubagentEndedAsync(SubagentEnded notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// The turn is sealed.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnTurnCompletedAsync(TurnCompleted notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnTurnCompletedAsync(TurnCompleted notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// The caller spoke over the reply (cut or recut).
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnReplyCutAsync(ReplyCut notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnReplyCutAsync(ReplyCut notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// The user or agent voice state changed.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnVoiceStateChangedAsync(VoiceStateChanged notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnVoiceStateChangedAsync(VoiceStateChanged notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Voice turn timing is known.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnTurnLatencyAsync(TurnLatency notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnTurnLatencyAsync(TurnLatency notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// The stage machine moved.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnStageChangedAsync(StageChanged notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnStageChangedAsync(StageChanged notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// A non-fatal fault.
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnFaultAsync(Fault notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnFaultAsync(Fault notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// A started call left. Once per call, also after .
/// The fact.
/// Cancelled when passes.
/// A task that completes when the hook is done with the notice.
- public virtual ValueTask OnCallEndedAsync(CallEnded notice, CancellationToken cancellationToken) => default;
+ public virtual ValueTask OnCallEndedAsync(CallEnded notice, CancellationToken cancellationToken)
+ {
+ return default;
+ }
/// Gets how a gate of this hook fails. Override per point to fail closed where the gate fails open.
/// The gate point.
diff --git a/src/AgentCore.Application/Hooks/BuiltIn/AuditHook.cs b/src/AgentCore.Application/Hooks/BuiltIn/AuditHook.cs
index 0e9fd22a..6dfbfa23 100644
--- a/src/AgentCore.Application/Hooks/BuiltIn/AuditHook.cs
+++ b/src/AgentCore.Application/Hooks/BuiltIn/AuditHook.cs
@@ -22,8 +22,10 @@ internal sealed class AuditHook(IAuditSinkPort sink, ILogger logger) : AgentHook
public override TimeSpan? NoticeTimeout => null;
///
- public override ValueTask OnConversationStartedAsync(ConversationStarted notice, CancellationToken cancellationToken) =>
- AppendAsync(notice, AuditEventKind.ConversationStarted, NoPayload, amends: null, cancellationToken);
+ public override ValueTask OnConversationStartedAsync(ConversationStarted notice, CancellationToken cancellationToken)
+ {
+ return AppendAsync(notice, AuditEventKind.ConversationStarted, NoPayload, amends: null, cancellationToken);
+ }
///
public override ValueTask OnConversationEndedAsync(ConversationEnded notice, CancellationToken cancellationToken)
@@ -38,32 +40,39 @@ public override ValueTask OnConversationEndedAsync(ConversationEnded notice, Can
}
///
- public override ValueTask OnTurnRefusedAsync(TurnRefused notice, CancellationToken cancellationToken) =>
- !AuditRules.IsAudited(notice)
+ public override ValueTask OnTurnRefusedAsync(TurnRefused notice, CancellationToken cancellationToken)
+ {
+ return !AuditRules.IsAudited(notice)
? ValueTask.CompletedTask
: AppendAsync(notice, AuditEventKind.TurnRefused,
new Dictionary(StringComparer.Ordinal) { [AuditPayloadKeys.RefusedReason] = TurnRefusalTokens.ToToken(notice.Reason) },
amends: null, cancellationToken);
+ }
///
- public override ValueTask OnTurnSupersededAsync(TurnSuperseded notice, CancellationToken cancellationToken) =>
- AppendAsync(notice, AuditEventKind.TurnSuperseded, new Dictionary(StringComparer.Ordinal)
+ public override ValueTask OnTurnSupersededAsync(TurnSuperseded notice, CancellationToken cancellationToken)
+ {
+ return AppendAsync(notice, AuditEventKind.TurnSuperseded, new Dictionary(StringComparer.Ordinal)
{
[AuditPayloadKeys.WithdrewFromTurnIndex] = notice.WithdrewFrom.ToString(CultureInfo.InvariantCulture),
[AuditPayloadKeys.WithdrewThroughTurnIndex] = notice.WithdrewThrough.ToString(CultureInfo.InvariantCulture),
}, amends: null, cancellationToken);
+ }
///
- public override ValueTask OnInputModeratedAsync(InputModerated notice, CancellationToken cancellationToken) =>
- !AuditRules.IsAudited(notice)
+ public override ValueTask OnInputModeratedAsync(InputModerated notice, CancellationToken cancellationToken)
+ {
+ return !AuditRules.IsAudited(notice)
? ValueTask.CompletedTask
: AppendAsync(notice, AuditEventKind.PromptFlagged,
new Dictionary(StringComparer.Ordinal) { [AuditPayloadKeys.ModerationCategories] = string.Join(',', notice.Categories) },
amends: null, cancellationToken);
+ }
///
- public override ValueTask OnToolCalledAsync(ToolCalled notice, CancellationToken cancellationToken) =>
- !AuditRules.IsAudited(notice)
+ public override ValueTask OnToolCalledAsync(ToolCalled notice, CancellationToken cancellationToken)
+ {
+ return !AuditRules.IsAudited(notice)
? ValueTask.CompletedTask
: AppendAsync(notice, AuditEventKind.ToolFailed, new Dictionary(StringComparer.Ordinal)
{
@@ -72,15 +81,18 @@ public override ValueTask OnToolCalledAsync(ToolCalled notice, CancellationToken
[AuditPayloadKeys.ToolFailureKind] = ToolFailureKinds.ToToken(notice.FailureKind ?? ToolFailureKind.Faulted),
[AuditPayloadKeys.ToolError] = notice.Failure ?? string.Empty,
}, amends: null, cancellationToken);
+ }
///
- public override ValueTask OnTurnCompletedAsync(TurnCompleted notice, CancellationToken cancellationToken) =>
- AppendAsync(notice, AuditEventKind.TurnCompleted, new Dictionary(StringComparer.Ordinal)
+ public override ValueTask OnTurnCompletedAsync(TurnCompleted notice, CancellationToken cancellationToken)
+ {
+ return AppendAsync(notice, AuditEventKind.TurnCompleted, new Dictionary(StringComparer.Ordinal)
{
[AuditPayloadKeys.ReplyTextSha256] = AuditHash.OfText(notice.ReplyText).Value,
[AuditPayloadKeys.StageBefore] = notice.StageBefore,
[AuditPayloadKeys.StageAfter] = notice.StageAfter,
}, amends: null, cancellationToken);
+ }
///
public override ValueTask OnReplyCutAsync(ReplyCut notice, CancellationToken cancellationToken)
diff --git a/src/AgentCore.Application/Hooks/BuiltIn/AuditRules.cs b/src/AgentCore.Application/Hooks/BuiltIn/AuditRules.cs
index 35efe788..17e8540a 100644
--- a/src/AgentCore.Application/Hooks/BuiltIn/AuditRules.cs
+++ b/src/AgentCore.Application/Hooks/BuiltIn/AuditRules.cs
@@ -9,13 +9,21 @@ namespace AgentCore.Application.Hooks.BuiltIn
internal static class AuditRules
{
/// A fault the model cannot answer, or a tool the model named that no one declared, is a tool.failed row.
- internal static bool IsAudited(ToolCalled notice) =>
- notice is { Outcome: ToolOutcome.Failed, Fatal: true } or { Outcome: ToolOutcome.Undeclared };
+ internal static bool IsAudited(ToolCalled notice)
+ {
+ return notice is { Outcome: ToolOutcome.Failed, Fatal: true } or { Outcome: ToolOutcome.Undeclared };
+ }
/// A refusal after conversation.ended is no row: nothing is appended behind the end.
- internal static bool IsAudited(TurnRefused notice) => !notice.AfterEnd;
+ internal static bool IsAudited(TurnRefused notice)
+ {
+ return !notice.AfterEnd;
+ }
/// Only flagged words are a prompt.flagged row.
- internal static bool IsAudited(InputModerated notice) => notice.Verdict == InputVerdict.Flagged;
+ internal static bool IsAudited(InputModerated notice)
+ {
+ return notice.Verdict == InputVerdict.Flagged;
+ }
}
}
diff --git a/src/AgentCore.Application/Hooks/BuiltIn/LoggingHook.cs b/src/AgentCore.Application/Hooks/BuiltIn/LoggingHook.cs
index bbdb6ba9..292526de 100644
--- a/src/AgentCore.Application/Hooks/BuiltIn/LoggingHook.cs
+++ b/src/AgentCore.Application/Hooks/BuiltIn/LoggingHook.cs
@@ -78,8 +78,14 @@ public override ValueTask OnFaultAsync(Fault notice, CancellationToken cancellat
return ValueTask.CompletedTask;
}
- private static string IdOf(HookNotice notice) => notice.Scope.ConversationId ?? string.Empty;
+ private static string IdOf(HookNotice notice)
+ {
+ return notice.Scope.ConversationId ?? string.Empty;
+ }
- private static int TurnOf(HookNotice notice) => notice.Scope.TurnIndex ?? NoTurn;
+ private static int TurnOf(HookNotice notice)
+ {
+ return notice.Scope.TurnIndex ?? NoTurn;
+ }
}
}
diff --git a/src/AgentCore.Application/Hooks/BuiltIn/TelemetryHook.cs b/src/AgentCore.Application/Hooks/BuiltIn/TelemetryHook.cs
index 8ef20414..7dc5c2e0 100644
--- a/src/AgentCore.Application/Hooks/BuiltIn/TelemetryHook.cs
+++ b/src/AgentCore.Application/Hooks/BuiltIn/TelemetryHook.cs
@@ -11,24 +11,34 @@ namespace AgentCore.Application.Hooks.BuiltIn
internal sealed class TelemetryHook : AgentHook
{
///
- public override ValueTask OnConversationStartedAsync(ConversationStarted notice, CancellationToken cancellationToken) =>
- Audited(AuditEventKind.ConversationStarted);
+ public override ValueTask OnConversationStartedAsync(ConversationStarted notice, CancellationToken cancellationToken)
+ {
+ return Audited(AuditEventKind.ConversationStarted);
+ }
///
- public override ValueTask OnConversationEndedAsync(ConversationEnded notice, CancellationToken cancellationToken) =>
- Audited(AuditEventKind.ConversationEnded);
+ public override ValueTask OnConversationEndedAsync(ConversationEnded notice, CancellationToken cancellationToken)
+ {
+ return Audited(AuditEventKind.ConversationEnded);
+ }
///
- public override ValueTask OnTurnRefusedAsync(TurnRefused notice, CancellationToken cancellationToken) =>
- AuditRules.IsAudited(notice) ? Audited(AuditEventKind.TurnRefused) : ValueTask.CompletedTask;
+ public override ValueTask OnTurnRefusedAsync(TurnRefused notice, CancellationToken cancellationToken)
+ {
+ return AuditRules.IsAudited(notice) ? Audited(AuditEventKind.TurnRefused) : ValueTask.CompletedTask;
+ }
///
- public override ValueTask OnTurnSupersededAsync(TurnSuperseded notice, CancellationToken cancellationToken) =>
- Audited(AuditEventKind.TurnSuperseded);
+ public override ValueTask OnTurnSupersededAsync(TurnSuperseded notice, CancellationToken cancellationToken)
+ {
+ return Audited(AuditEventKind.TurnSuperseded);
+ }
///
- public override ValueTask OnReplyCutAsync(ReplyCut notice, CancellationToken cancellationToken) =>
- Audited(AuditEventKind.ReplyInterrupted);
+ public override ValueTask OnReplyCutAsync(ReplyCut notice, CancellationToken cancellationToken)
+ {
+ return Audited(AuditEventKind.ReplyInterrupted);
+ }
///
public override ValueTask OnInputModeratedAsync(InputModerated notice, CancellationToken cancellationToken)
@@ -45,8 +55,10 @@ public override ValueTask OnInputModeratedAsync(InputModerated notice, Cancellat
}
///
- public override ValueTask OnToolCalledAsync(ToolCalled notice, CancellationToken cancellationToken) =>
- AuditRules.IsAudited(notice) ? Audited(AuditEventKind.ToolFailed) : ValueTask.CompletedTask;
+ public override ValueTask OnToolCalledAsync(ToolCalled notice, CancellationToken cancellationToken)
+ {
+ return AuditRules.IsAudited(notice) ? Audited(AuditEventKind.ToolFailed) : ValueTask.CompletedTask;
+ }
///
public override ValueTask OnTurnCompletedAsync(TurnCompleted notice, CancellationToken cancellationToken)
diff --git a/src/AgentCore.Application/Hooks/BuiltIn/TurnFailureAccounting.cs b/src/AgentCore.Application/Hooks/BuiltIn/TurnFailureAccounting.cs
index 538c5001..b4f92619 100644
--- a/src/AgentCore.Application/Hooks/BuiltIn/TurnFailureAccounting.cs
+++ b/src/AgentCore.Application/Hooks/BuiltIn/TurnFailureAccounting.cs
@@ -31,13 +31,16 @@ internal enum Row
/// How the turn ended.
/// Whether the fallback came from a tool that spent its retry budget.
/// The row, or .
- internal static Row Of(TurnOutcome outcome, bool failedInTool) => (outcome, failedInTool) switch
+ internal static Row Of(TurnOutcome outcome, bool failedInTool)
{
- (TurnOutcome.Empty, _) => Row.EmptyReply,
- (TurnOutcome.Fallback, true) => Row.ToolBudget,
- (TurnOutcome.Fallback or TurnOutcome.Faulted, _) => Row.RunFault,
- _ => Row.None,
- };
+ return (outcome, failedInTool) switch
+ {
+ (TurnOutcome.Empty, _) => Row.EmptyReply,
+ (TurnOutcome.Fallback, true) => Row.ToolBudget,
+ (TurnOutcome.Fallback or TurnOutcome.Faulted, _) => Row.RunFault,
+ _ => Row.None,
+ };
+ }
/// Counts the row on agentcore.turn.failures.
/// The row.
diff --git a/src/AgentCore.Application/Hooks/Engine/GateRunner.cs b/src/AgentCore.Application/Hooks/Engine/GateRunner.cs
index 92d0cc13..518f629a 100644
--- a/src/AgentCore.Application/Hooks/Engine/GateRunner.cs
+++ b/src/AgentCore.Application/Hooks/Engine/GateRunner.cs
@@ -11,7 +11,10 @@ internal sealed class GateRunner(HookTable table, ILogger logger, TimeProvider t
{
internal HookTable Table => table;
- internal bool Overrides(GatePoint point) => table.Overrides(point);
+ internal bool Overrides(GatePoint point)
+ {
+ return table.Overrides(point);
+ }
/// Runs the chain and returns the value the hooks left.
/// The gate's view type.
@@ -89,8 +92,10 @@ private static HookFailure FailureOf(AgentHook hook, GatePoint point)
}
// A hook's own TimeoutException faulted its task; the wait's own timeout is a fresh exception.
- private static bool ThrownBy(Task running, Exception exception) =>
- running.Exception?.InnerExceptions.Contains(exception) == true;
+ private static bool ThrownBy(Task running, Exception exception)
+ {
+ return running.Exception?.InnerExceptions.Contains(exception) == true;
+ }
// Disposing a source unhooks it from the turn token and stops its timer, so a hook still running would
// never see its token cancelled. The sources live until the hook ends, however long that is.
diff --git a/src/AgentCore.Application/Hooks/Engine/HookReader.cs b/src/AgentCore.Application/Hooks/Engine/HookReader.cs
index a03a7d1a..958472f9 100644
--- a/src/AgentCore.Application/Hooks/Engine/HookReader.cs
+++ b/src/AgentCore.Application/Hooks/Engine/HookReader.cs
@@ -43,7 +43,10 @@ internal HookReader(Mailbox box, AgentHook hook, IReadOnlySet wants)
internal bool IsIdle => Volatile.Read(ref _pending) == 0;
- internal bool Wants(HookNotice notice) => _wants.Contains(notice.GetType());
+ internal bool Wants(HookNotice notice)
+ {
+ return _wants.Contains(notice.GetType());
+ }
/// Queues one notice. Runs under the mailbox lock, so queue order is Sequence order.
internal void Write(HookNotice notice)
@@ -108,7 +111,10 @@ internal void Complete()
};
}
- private static string NameOf(AgentHook hook) => hook.GetType().FullName ?? hook.GetType().Name;
+ private static string NameOf(AgentHook hook)
+ {
+ return hook.GetType().FullName ?? hook.GetType().Name;
+ }
// The reader lives as long as the mailbox. Started with the raiser's context, every later delivery would
// run inside the first turn's trace, logging scopes and run context, and keep them alive.
diff --git a/src/AgentCore.Application/Hooks/Engine/HookRuntime.cs b/src/AgentCore.Application/Hooks/Engine/HookRuntime.cs
index 880483c1..d749bd55 100644
--- a/src/AgentCore.Application/Hooks/Engine/HookRuntime.cs
+++ b/src/AgentCore.Application/Hooks/Engine/HookRuntime.cs
@@ -54,7 +54,11 @@ internal HookRuntime WithNoticeHooks(IEnumerable hooks)
AgentHook[] added = [.. hooks];
foreach (AgentHook hook in added)
{
- ArgumentNullException.ThrowIfNull(hook, nameof(hooks));
+ if (hook is null)
+ {
+ throw new ArgumentNullException(nameof(hooks), "A hook in the list is null.");
+ }
+
if (!Table.Hooks.Contains(hook, ReferenceEqualityComparer.Instance) && HookTable.Build([hook]).OverridesAnyGate(hook))
{
throw new ArgumentException(
@@ -74,9 +78,15 @@ internal void RaiseHostFault(Fault fault, AgentHook except)
_ = Notices.Raise(fault, Timers, except: except);
}
- internal ValueTask StopAsync(TimeSpan timeout) => Notices.StopAsync(timeout);
+ internal ValueTask StopAsync(TimeSpan timeout)
+ {
+ return Notices.StopAsync(timeout);
+ }
///
- public ValueTask DisposeAsync() => StopAsync(StopTimeout);
+ public ValueTask DisposeAsync()
+ {
+ return StopAsync(StopTimeout);
+ }
}
}
diff --git a/src/AgentCore.Application/Hooks/Engine/HookScopes.cs b/src/AgentCore.Application/Hooks/Engine/HookScopes.cs
index 78649038..c36aab67 100644
--- a/src/AgentCore.Application/Hooks/Engine/HookScopes.cs
+++ b/src/AgentCore.Application/Hooks/Engine/HookScopes.cs
@@ -3,9 +3,14 @@ namespace AgentCore.Application.Hooks.Engine
/// Scopes for the gates and notices that have no session.
internal static class HookScopes
{
- internal static HookScope Host(DateTimeOffset now) => new(null, null, null, null, Guid.Empty, 0, now);
+ internal static HookScope Host(DateTimeOffset now)
+ {
+ return new(null, null, null, null, Guid.Empty, 0, now);
+ }
- internal static HookScope ForGate(string? conversationId, string? entry, DateTimeOffset now) =>
- new(conversationId, entry, null, null, Guid.Empty, 0, now);
+ internal static HookScope ForGate(string? conversationId, string? entry, DateTimeOffset now)
+ {
+ return new(conversationId, entry, null, null, Guid.Empty, 0, now);
+ }
}
}
diff --git a/src/AgentCore.Application/Hooks/Engine/HookTable.cs b/src/AgentCore.Application/Hooks/Engine/HookTable.cs
index acce6e8a..ef0791a0 100644
--- a/src/AgentCore.Application/Hooks/Engine/HookTable.cs
+++ b/src/AgentCore.Application/Hooks/Engine/HookTable.cs
@@ -66,7 +66,11 @@ internal static HookTable Build(IReadOnlyList hooks)
HashSet seen = new(ReferenceEqualityComparer.Instance);
foreach (AgentHook hook in hooks)
{
- ArgumentNullException.ThrowIfNull(hook, nameof(hooks));
+ if (hook is null)
+ {
+ throw new ArgumentNullException(nameof(hooks), "A hook in the list is null.");
+ }
+
if (seen.Add(hook))
{
distinct.Add(hook);
@@ -76,17 +80,30 @@ internal static HookTable Build(IReadOnlyList hooks)
return new HookTable(distinct);
}
- internal AgentHook[] For(GatePoint point) => _gates[point];
+ internal AgentHook[] For(GatePoint point)
+ {
+ return _gates[point];
+ }
- internal bool Overrides(GatePoint point) => _gates[point].Length > 0;
+ internal bool Overrides(GatePoint point)
+ {
+ return _gates[point].Length > 0;
+ }
- internal IReadOnlySet NoticesOf(AgentHook hook) =>
- _notices.TryGetValue(hook, out IReadOnlySet? types) ? types : NoNotices;
+ internal IReadOnlySet NoticesOf(AgentHook hook)
+ {
+ return _notices.TryGetValue(hook, out IReadOnlySet? types) ? types : NoNotices;
+ }
- internal bool Wants(Type noticeType) => _wanted.Contains(noticeType);
+ internal bool Wants(Type noticeType)
+ {
+ return _wanted.Contains(noticeType);
+ }
- internal bool OverridesAnyGate(AgentHook hook) =>
- GatePoint.All.Any(point => Array.Exists(_gates[point], listed => ReferenceEquals(listed, hook)));
+ internal bool OverridesAnyGate(AgentHook hook)
+ {
+ return GatePoint.All.Any(point => Array.Exists(_gates[point], listed => ReferenceEquals(listed, hook)));
+ }
/// Whether a hook type overrides one gate, read from the type alone.
/// The hook's type.
@@ -97,7 +114,10 @@ internal static bool TypeOverrides(Type hookType, GatePoint point)
return TypeOverrides(hookType, typeof(AgentHook).GetMethod(point.MethodName)!);
}
- private static bool Overrides(AgentHook hook, MethodInfo baseMethod) => TypeOverrides(hook.GetType(), baseMethod);
+ private static bool Overrides(AgentHook hook, MethodInfo baseMethod)
+ {
+ return TypeOverrides(hook.GetType(), baseMethod);
+ }
// GetBaseDefinition tells an override apart from a method hidden with `new`, which the base dispatch never calls.
private static bool TypeOverrides(Type hookType, MethodInfo baseMethod)
diff --git a/src/AgentCore.Application/Hooks/Engine/NoticeHub.cs b/src/AgentCore.Application/Hooks/Engine/NoticeHub.cs
index 95c935b8..b6121bd2 100644
--- a/src/AgentCore.Application/Hooks/Engine/NoticeHub.cs
+++ b/src/AgentCore.Application/Hooks/Engine/NoticeHub.cs
@@ -28,7 +28,10 @@ internal sealed class NoticeHub(HookTable table, ILogger logger, TimeProvider ti
internal int MailboxCount => _mailboxes.Count;
- internal bool Wants(Type noticeType) => table.Wants(noticeType);
+ internal bool Wants(Type noticeType)
+ {
+ return table.Wants(noticeType);
+ }
/// Stamps one notice and queues it for every hook that wants it.
/// The fact, with its scope's conversation, session and turn already set.
diff --git a/src/AgentCore.Application/Hooks/Engine/SessionHooks.cs b/src/AgentCore.Application/Hooks/Engine/SessionHooks.cs
index 0bb742fb..d752451e 100644
--- a/src/AgentCore.Application/Hooks/Engine/SessionHooks.cs
+++ b/src/AgentCore.Application/Hooks/Engine/SessionHooks.cs
@@ -49,16 +49,24 @@ internal SessionHooks(HookRuntime runtime, string conversationId, string entry,
///
internal ConcurrentDictionary ApprovalDecisions { get; } = new(StringComparer.Ordinal);
- internal HookScope Scope(int? turnIndex, string? stage) =>
- new(_conversationId, _entry, turnIndex, stage, SessionId, 0, _time.GetUtcNow());
+ internal HookScope Scope(int? turnIndex, string? stage)
+ {
+ return new(_conversationId, _entry, turnIndex, stage, SessionId, 0, _time.GetUtcNow());
+ }
internal bool Wants()
- where TNotice : HookNotice => _runtime.Notices.Wants(typeof(TNotice));
+ where TNotice : HookNotice
+ {
+ return _runtime.Notices.Wants(typeof(TNotice));
+ }
/// Raises one notice of this session.
/// The fact.
/// When it happened, if it was read before the raise (a turn's end), or for now.
- internal Guid Raise(HookNotice notice, DateTimeOffset? occurredAt = null) => _runtime.Notices.Raise(notice, _time, occurredAt: occurredAt);
+ internal Guid Raise(HookNotice notice, DateTimeOffset? occurredAt = null)
+ {
+ return _runtime.Notices.Raise(notice, _time, occurredAt: occurredAt);
+ }
///
/// Raises , once per session. The caller holds the session's turn lock, so the
@@ -112,7 +120,10 @@ internal ConversationOrigin OriginOf(bool storeHeldConversation)
return storeHeldConversation ? ConversationOrigin.Resumed : ConversationOrigin.New;
}
- internal Task FlushAsync() => _runtime.Notices.FlushAsync(_conversationId);
+ internal Task FlushAsync()
+ {
+ return _runtime.Notices.FlushAsync(_conversationId);
+ }
/// Lets go of the mailbox pin, once. The session was disposed or unloaded.
internal void Release()
diff --git a/src/AgentCore.Application/Hooks/Engine/TurnRounds.cs b/src/AgentCore.Application/Hooks/Engine/TurnRounds.cs
index 4174dc4c..01dd79f1 100644
--- a/src/AgentCore.Application/Hooks/Engine/TurnRounds.cs
+++ b/src/AgentCore.Application/Hooks/Engine/TurnRounds.cs
@@ -5,7 +5,10 @@ internal sealed class TurnRounds
{
private int _next = -1;
- internal int Next() => Interlocked.Increment(ref _next);
+ internal int Next()
+ {
+ return Interlocked.Increment(ref _next);
+ }
/// Gets how many model round trips the turn has made so far.
internal int Count => Volatile.Read(ref _next) + 1;
diff --git a/src/AgentCore.Application/Hooks/Layers/ApprovalGateLayer.cs b/src/AgentCore.Application/Hooks/Layers/ApprovalGateLayer.cs
index 74fb28b0..8b789da8 100644
--- a/src/AgentCore.Application/Hooks/Layers/ApprovalGateLayer.cs
+++ b/src/AgentCore.Application/Hooks/Layers/ApprovalGateLayer.cs
@@ -157,10 +157,16 @@ private static ChatMessage Answer(List<(ToolApprovalRequestContent Request, stri
return new(ChatRole.User, [.. denied.Select(static pair => (AIContent)pair.Request.CreateResponse(false, pair.Reason))]);
}
- private static int? RoundsSoFar(AgentRunOptions? options) => TurnInvocation.From(options)?.Rounds?.Count;
+ private static int? RoundsSoFar(AgentRunOptions? options)
+ {
+ return TurnInvocation.From(options)?.Rounds?.Count;
+ }
// Without a round counter every run counts, which can only end the run sooner.
- private static bool CalledTheModel(int? before, AgentRunOptions? options) => before is null || RoundsSoFar(options) != before;
+ private static bool CalledTheModel(int? before, AgentRunOptions? options)
+ {
+ return before is null || RoundsSoFar(options) != before;
+ }
/// The cap is reached with a denial still open: never a person's to answer. Logs, raises the fault, and throws.
private static InvalidOperationException Exhausted(List<(ToolApprovalRequestContent Request, string Reason)> denied, AgentRunOptions? options)
diff --git a/src/AgentCore.Application/Hooks/Layers/ModelHookChatClient.cs b/src/AgentCore.Application/Hooks/Layers/ModelHookChatClient.cs
index b25f2627..603fd63c 100644
--- a/src/AgentCore.Application/Hooks/Layers/ModelHookChatClient.cs
+++ b/src/AgentCore.Application/Hooks/Layers/ModelHookChatClient.cs
@@ -198,7 +198,6 @@ public override async IAsyncEnumerable GetStreamingResponseA
{
foreach (ChatResponseUpdate update in seen)
{
- yielded = true;
yield return update;
}
}
@@ -230,6 +229,9 @@ public override async IAsyncEnumerable GetStreamingResponseA
}
}
- private static TurnInvocation? Turn() => TurnInvocation.From(AIAgent.CurrentRunContext?.RunOptions);
+ private static TurnInvocation? Turn()
+ {
+ return TurnInvocation.From(AIAgent.CurrentRunContext?.RunOptions);
+ }
}
}
diff --git a/src/AgentCore.Application/Hooks/Layers/ModelRoundGates.cs b/src/AgentCore.Application/Hooks/Layers/ModelRoundGates.cs
index 77adef67..07a7e382 100644
--- a/src/AgentCore.Application/Hooks/Layers/ModelRoundGates.cs
+++ b/src/AgentCore.Application/Hooks/Layers/ModelRoundGates.cs
@@ -59,7 +59,10 @@ internal static ValueTask FailedAsync(
cancellationToken);
}
- private static ChatResponse Refusal(ModelRound round) => new(new ChatMessage(ChatRole.Assistant, round.RefusalReply));
+ private static ChatResponse Refusal(ModelRound round)
+ {
+ return new(new ChatMessage(ChatRole.Assistant, round.RefusalReply));
+ }
/// What the model is asked after BeforeModel, or the answer a hook gave in its place.
internal sealed record Asked(IReadOnlyList Messages, ChatOptions? Options, ChatResponse? Answer);
diff --git a/src/AgentCore.Application/Hooks/Layers/ToolCallOutcomes.cs b/src/AgentCore.Application/Hooks/Layers/ToolCallOutcomes.cs
index dc72fd63..67653dd6 100644
--- a/src/AgentCore.Application/Hooks/Layers/ToolCallOutcomes.cs
+++ b/src/AgentCore.Application/Hooks/Layers/ToolCallOutcomes.cs
@@ -21,12 +21,16 @@ internal static void Mark(AIFunctionArguments arguments, object key)
arguments.Context[key] = true;
}
- internal static bool Marked(AIFunctionArguments arguments, object key) =>
- arguments.Context?.ContainsKey(key) == true;
+ internal static bool Marked(AIFunctionArguments arguments, object key)
+ {
+ return arguments.Context?.ContainsKey(key) == true;
+ }
- internal static ToolOutcome Of(AIFunctionArguments arguments) =>
- Marked(arguments, TimedOutKey) ? ToolOutcome.TimedOut
+ internal static ToolOutcome Of(AIFunctionArguments arguments)
+ {
+ return Marked(arguments, TimedOutKey) ? ToolOutcome.TimedOut
: Marked(arguments, CachedKey) ? ToolOutcome.Cached
: ToolOutcome.Ok;
+ }
}
}
diff --git a/src/AgentCore.Application/Hooks/Layers/ToolHookMiddleware.cs b/src/AgentCore.Application/Hooks/Layers/ToolHookMiddleware.cs
index 9ec4b75e..ef2cc060 100644
--- a/src/AgentCore.Application/Hooks/Layers/ToolHookMiddleware.cs
+++ b/src/AgentCore.Application/Hooks/Layers/ToolHookMiddleware.cs
@@ -42,8 +42,10 @@ internal static AIAgent Apply(AIAgent agent, HookRuntime hooks)
.Build();
}
- private static AgentRunOptions? Fresh(AgentRunOptions? options) =>
- options is ChatClientAgentRunOptions run ? run.Clone() : options;
+ private static AgentRunOptions? Fresh(AgentRunOptions? options)
+ {
+ return options is ChatClientAgentRunOptions run ? run.Clone() : options;
+ }
private sealed record Before(Dictionary Arguments, bool Blocked, bool Responded, object? Result, bool EndLoop);
@@ -227,9 +229,11 @@ private static void RaiseSkillLoaded(Call call, AIFunctionArguments arguments, o
}
// MAF's load_skill answers an unknown name with this text (SkillsProviderFactoryTests pins it), serialized.
- private static bool SkillNotFound(string skill, object? result) =>
- result is JsonElement { ValueKind: JsonValueKind.String } text
+ private static bool SkillNotFound(string skill, object? result)
+ {
+ return result is JsonElement { ValueKind: JsonValueKind.String } text
&& string.Equals(text.GetString(), $"Error: Skill '{skill}' not found.", StringComparison.Ordinal);
+ }
// A new object, because the old one wraps the model's own call record, which is stored and sent back.
// It shares the old Context: that is where the turn is filed and where the wrappers leave their marks.
diff --git a/src/AgentCore.Application/Hooks/Layers/TurnGateAgent.cs b/src/AgentCore.Application/Hooks/Layers/TurnGateAgent.cs
index 5769f065..23937d65 100644
--- a/src/AgentCore.Application/Hooks/Layers/TurnGateAgent.cs
+++ b/src/AgentCore.Application/Hooks/Layers/TurnGateAgent.cs
@@ -91,7 +91,9 @@ protected override async IAsyncEnumerable RunCoreStreamingA
}
}
- private static TurnInvocation? Filed(AgentRunOptions? options) =>
- TurnInvocation.From(options) is { Nested: false, User: not null, Hooks: not null } turn ? turn : null;
+ private static TurnInvocation? Filed(AgentRunOptions? options)
+ {
+ return TurnInvocation.From(options) is { Nested: false, User: not null, Hooks: not null } turn ? turn : null;
+ }
}
}
diff --git a/src/AgentCore.Application/Llm/CompositeChatClientFactory.cs b/src/AgentCore.Application/Llm/CompositeChatClientFactory.cs
index 2305c055..54711abf 100644
--- a/src/AgentCore.Application/Llm/CompositeChatClientFactory.cs
+++ b/src/AgentCore.Application/Llm/CompositeChatClientFactory.cs
@@ -89,7 +89,7 @@ public static async ValueTask CreateAsync(
ModelCatalogEntry? found = await catalog
.LookupAsync(entry.Kind, entry.Model, cancellationToken)
.ConfigureAwait(false);
-
+
factory._contextWindows[entry.As] = found?.ContextWindow;
}
diff --git a/src/AgentCore.Application/Ports/IConversations.cs b/src/AgentCore.Application/Ports/IConversations.cs
index 89ec85af..f91b47a3 100644
--- a/src/AgentCore.Application/Ports/IConversations.cs
+++ b/src/AgentCore.Application/Ports/IConversations.cs
@@ -1,3 +1,4 @@
+using AgentCore.Application.Blobs;
using System.Text.Json;
using AgentCore.Application.Conversation;
using AgentCore.Application.Transcript;
@@ -16,7 +17,7 @@ public interface IConversations
///
ValueTask GetAsync(string conversationId, CancellationToken cancellationToken = default);
- /// Reads one window of a conversation: its row, the newest turns before a given one, and a link to every file those turns kept.
+ /// Reads one window of a conversation: its row, the newest turns before a given one, and the facts of every file those turns kept.
/// The conversation to read.
/// Which turns: how many, and before which.
/// Cancels the read.
@@ -26,6 +27,19 @@ public interface IConversations
///
ValueTask LoadWindowAsync(string conversationId, TranscriptWindow window, CancellationToken cancellationToken = default);
+ ///
+ /// Makes a fresh, short-lived link to one file a conversation kept. Call it when the person asks for the file.
+ /// A host that proxies the bytes itself, or needs a different lifetime, goes through instead.
+ ///
+ /// The conversation that owns the file. The caller has already checked who may read it.
+ /// The file's name, as lists it.
+ /// Cancels the lookup and the signing.
+ ///
+ /// The URL, or when the host opened no blob store, the conversation id fails
+ /// or the name fails (blank included; this never throws for either), the store holds no blob under that owner and name, or the store has no web door.
+ ///
+ ValueTask LinkFileAsync(string conversationId, string name, CancellationToken cancellationToken = default);
+
///
ValueTask ListAsync(
string principalKey,
diff --git a/src/AgentCore.Application/PublicAPI.Unshipped.txt b/src/AgentCore.Application/PublicAPI.Unshipped.txt
index e7934d94..7fe2746d 100644
--- a/src/AgentCore.Application/PublicAPI.Unshipped.txt
+++ b/src/AgentCore.Application/PublicAPI.Unshipped.txt
@@ -2032,15 +2032,6 @@ static AgentCore.Application.Blobs.BlobLink.Lifetime.get -> System.TimeSpan
static AgentCore.Application.Blobs.BlobLink.DispositionOf(string! mediaType, string! name) -> string!
AgentCore.Application.Ports.IBlobStore.LinkAsync(AgentCore.Application.Blobs.BlobRef! blob, System.TimeSpan lifetime, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
AgentCore.Application.Ports.IBlobStore.StatAsync(string! ownerId, string! name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
-AgentCore.Application.Blobs.FileLink
-AgentCore.Application.Blobs.FileLink.$() -> AgentCore.Application.Blobs.FileLink!
-AgentCore.Application.Blobs.FileLink.Blob.get -> AgentCore.Application.Blobs.BlobRef!
-AgentCore.Application.Blobs.FileLink.Blob.init -> void
-AgentCore.Application.Blobs.FileLink.Deconstruct(out AgentCore.Application.Blobs.BlobRef! Blob, out System.Uri? Url) -> void
-AgentCore.Application.Blobs.FileLink.Equals(AgentCore.Application.Blobs.FileLink? other) -> bool
-AgentCore.Application.Blobs.FileLink.FileLink(AgentCore.Application.Blobs.BlobRef! Blob, System.Uri? Url) -> void
-AgentCore.Application.Blobs.FileLink.Url.get -> System.Uri?
-AgentCore.Application.Blobs.FileLink.Url.init -> void
AgentCore.Application.Conversation.Conversations
AgentCore.Application.Conversation.Conversations.AppendAsync(string! conversationId, System.Collections.Generic.IReadOnlyList! messages, AgentCore.Application.Conversation.ConversationSessionState? state = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask!>
AgentCore.Application.Conversation.Conversations.SaveStateAsync(string! conversationId, AgentCore.Application.Conversation.ConversationSessionState! state, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
@@ -2054,7 +2045,6 @@ AgentCore.Application.Conversation.Conversations.DetachPrincipalAsync(string! co
AgentCore.Application.Conversation.Conversations.EraseAsync(string! conversationId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
AgentCore.Application.Conversation.Conversations.GetAsync(string! conversationId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
AgentCore.Application.Conversation.Conversations.FindContinuationAsync(string! responseId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
-AgentCore.Application.Conversation.Conversations.LinkFilesAsync(string! conversationId, System.Collections.Generic.IEnumerable! messages, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>!
AgentCore.Application.Conversation.Conversations.ListAsync(string! principalKey, string? after, int limit, AgentCore.Application.Conversation.ConversationStatus? status = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
AgentCore.Application.Conversation.Conversations.RenameAsync(string! conversationId, string! title, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
AgentCore.Application.Conversation.Conversations.RewriteAsync(string! conversationId, string! messageId, Microsoft.Extensions.AI.ChatMessage! content, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
@@ -2063,11 +2053,6 @@ AgentCore.Application.Conversation.Conversations.SetCustomAsync(string! conversa
AgentCore.Application.Conversation.Conversations.SetExternalIdAsync(string! conversationId, string? externalId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
AgentCore.Application.Conversation.Conversations.SetStatusAsync(string! conversationId, AgentCore.Application.Conversation.ConversationStatus status, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
AgentCore.Application.Conversation.Conversations.SweepAsync(System.TimeSpan retention, int batchSize = 500, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
-override AgentCore.Application.Blobs.FileLink.Equals(object? obj) -> bool
-override AgentCore.Application.Blobs.FileLink.GetHashCode() -> int
-override AgentCore.Application.Blobs.FileLink.ToString() -> string!
-static AgentCore.Application.Blobs.FileLink.operator ==(AgentCore.Application.Blobs.FileLink? left, AgentCore.Application.Blobs.FileLink? right) -> bool
-static AgentCore.Application.Blobs.FileLink.operator !=(AgentCore.Application.Blobs.FileLink? left, AgentCore.Application.Blobs.FileLink? right) -> bool
AgentCore.Application.Conversation.Conversations.Store.get -> AgentCore.Application.Ports.IConversationStore!
AgentCore.Application.Transcript.FileContent
AgentCore.Application.Transcript.FileContent.FileId.get -> string!
@@ -2118,11 +2103,9 @@ AgentCore.Application.Configuration.Compilation.AgentCompilationContext.Hooks.in
AgentCore.Application.Conversation.StoredConversation
AgentCore.Application.Conversation.StoredConversation.Conversation.get -> AgentCore.Application.Conversation.ConversationRecord!
AgentCore.Application.Conversation.StoredConversation.Conversation.init -> void
-AgentCore.Application.Conversation.StoredConversation.Files.get -> System.Collections.Generic.IReadOnlyList!
AgentCore.Application.Conversation.StoredConversation.Files.init -> void
AgentCore.Application.Conversation.StoredConversation.Messages.get -> System.Collections.Generic.IReadOnlyList!
AgentCore.Application.Conversation.StoredConversation.Messages.init -> void
-AgentCore.Application.Conversation.StoredConversation.StoredConversation(AgentCore.Application.Conversation.ConversationRecord! Conversation, System.Collections.Generic.IReadOnlyList! Messages, System.Collections.Generic.IReadOnlyList! Files) -> void
AgentCore.Application.Ports.IConversations
AgentCore.Application.Ports.IConversations.AppendMessageAsync(string! conversationId, Microsoft.Extensions.AI.ChatMessage! message, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
AgentCore.Application.Ports.IConversations.AttachPrincipalAsync(string! conversationId, string! principalKey, string! role, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
@@ -2137,7 +2120,6 @@ AgentCore.Application.Ports.IConversations.SetCustomAsync(string! conversationId
AgentCore.Application.Ports.IConversations.SetExternalIdAsync(string! conversationId, string? externalId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
AgentCore.Application.Ports.IConversations.SetStatusAsync(string! conversationId, AgentCore.Application.Conversation.ConversationStatus status, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
AgentCore.Application.Conversation.StoredConversation.$() -> AgentCore.Application.Conversation.StoredConversation!
-AgentCore.Application.Conversation.StoredConversation.Deconstruct(out AgentCore.Application.Conversation.ConversationRecord! Conversation, out System.Collections.Generic.IReadOnlyList! Messages, out System.Collections.Generic.IReadOnlyList! Files) -> void
AgentCore.Application.Conversation.StoredConversation.Equals(AgentCore.Application.Conversation.StoredConversation? other) -> bool
override AgentCore.Application.Conversation.StoredConversation.Equals(object? obj) -> bool
override AgentCore.Application.Conversation.StoredConversation.GetHashCode() -> int
@@ -3192,3 +3174,10 @@ static AgentCore.Application.Conversation.Commands.TransferCommand.operator !=(A
virtual AgentCore.Application.Conversation.Commands.ChannelCommand.EqualityContract.get -> System.Type!
virtual AgentCore.Application.Conversation.Commands.ChannelCommand.Equals(AgentCore.Application.Conversation.Commands.ChannelCommand? other) -> bool
virtual AgentCore.Application.Conversation.Commands.ChannelCommand.PrintMembers(System.Text.StringBuilder! builder) -> bool
+AgentCore.Application.Conversation.Conversations.LinkFileAsync(string! conversationId, string! name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
+AgentCore.Application.Conversation.StoredConversation.Deconstruct(out AgentCore.Application.Conversation.ConversationRecord! Conversation, out System.Collections.Generic.IReadOnlyList! Messages, out System.Collections.Generic.IReadOnlyList! Files) -> void
+AgentCore.Application.Conversation.StoredConversation.Files.get -> System.Collections.Generic.IReadOnlyList!
+AgentCore.Application.Conversation.StoredConversation.StoredConversation(AgentCore.Application.Conversation.ConversationRecord! Conversation, System.Collections.Generic.IReadOnlyList! Messages, System.Collections.Generic.IReadOnlyList! Files) -> void
+AgentCore.Application.Ports.IConversations.LinkFileAsync(string! conversationId, string! name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
+AgentCore.Application.Blobs.BlobOwner
+static AgentCore.Application.Blobs.BlobOwner.IsSafe(string? ownerId) -> bool
diff --git a/src/AgentCore.Application/Runtime/Compaction/NoticingChatClient.cs b/src/AgentCore.Application/Runtime/Compaction/NoticingChatClient.cs
index 6894673b..b6c61f17 100644
--- a/src/AgentCore.Application/Runtime/Compaction/NoticingChatClient.cs
+++ b/src/AgentCore.Application/Runtime/Compaction/NoticingChatClient.cs
@@ -8,15 +8,9 @@ namespace AgentCore.Application.Runtime.Compaction
/// Wraps the summariser for one invocation and posts the start notice at the one point that
/// actually means "the summariser is being called": the first request this client forwards.
///
- internal sealed class NoticingChatClient : DelegatingChatClient
+ internal sealed class NoticingChatClient(IChatClient inner, TurnNotices? notices) : DelegatingChatClient(inner)
{
- private readonly TurnNotices? _notices;
-
- public NoticingChatClient(IChatClient inner, TurnNotices? notices)
- : base(inner)
- {
- _notices = notices;
- }
+ private readonly TurnNotices? _notices = notices;
/// Gets whether this client has forwarded a request yet.
public bool Called { get; private set; }
diff --git a/src/AgentCore.Application/Runtime/Compaction/SummaryRowProvider.cs b/src/AgentCore.Application/Runtime/Compaction/SummaryRowProvider.cs
index 18807d33..bbbab9e4 100644
--- a/src/AgentCore.Application/Runtime/Compaction/SummaryRowProvider.cs
+++ b/src/AgentCore.Application/Runtime/Compaction/SummaryRowProvider.cs
@@ -22,6 +22,8 @@ namespace AgentCore.Application.Runtime.Compaction
#pragma warning disable MAAI001 // Compaction is evaluation-only in Microsoft.Agents.AI 1.21.0.
internal sealed class SummaryRowProvider : AIContextProvider
{
+ private const string Unchanged = "unchanged";
+
private readonly AgentCoreChatHistoryProvider _history;
private readonly IChatClient _summariser;
@@ -81,14 +83,14 @@ protected override async ValueTask InvokingCoreAsync(
if (gated.Cancelled)
{
RaiseCompacted(turn, gated, messages.Count, messages.Count, CompactionOutcome.Cancelled);
- PostEnd(turn, client, "unchanged");
+ PostEnd(turn, client, Unchanged);
return input;
}
if (output.Count == view.Count && output.Zip(view).All(pair => ReferenceEquals(pair.First, pair.Second)))
{
RaiseCompacted(turn, gated, messages.Count, messages.Count, CompactionOutcome.Unchanged);
- PostEnd(turn, client, "unchanged");
+ PostEnd(turn, client, Unchanged);
return input;
}
@@ -100,7 +102,7 @@ protected override async ValueTask InvokingCoreAsync(
}
RaiseCompacted(turn, gated, messages.Count, messages.Count, CompactionOutcome.Unchanged);
- PostEnd(turn, client, "unchanged");
+ PostEnd(turn, client, Unchanged);
return input;
}
@@ -108,7 +110,7 @@ protected override async ValueTask InvokingCoreAsync(
if (coversUpTo <= floor.CoversUpTo || !_history.Compact(session, output[0], coversUpTo, floor.Revision))
{
RaiseCompacted(turn, gated, messages.Count, messages.Count, CompactionOutcome.Unchanged);
- PostEnd(turn, client, "unchanged");
+ PostEnd(turn, client, Unchanged);
return input;
}
@@ -128,7 +130,7 @@ protected override async ValueTask InvokingCoreAsync(
if (gated.Cancelled)
{
RaiseCompacted(turn, gated, messages.Count, messages.Count, CompactionOutcome.Cancelled);
- PostEnd(turn, client, "unchanged");
+ PostEnd(turn, client, Unchanged);
return input;
}
diff --git a/src/AgentCore.Application/Runtime/Cut/ConversationCutTracker.cs b/src/AgentCore.Application/Runtime/Cut/ConversationCutTracker.cs
index 1c7813a2..7d31b011 100644
--- a/src/AgentCore.Application/Runtime/Cut/ConversationCutTracker.cs
+++ b/src/AgentCore.Application/Runtime/Cut/ConversationCutTracker.cs
@@ -36,9 +36,9 @@ internal ConversationCutTracker(ConversationSession session)
internal bool Cut(int turnIndex, TurnCut cut)
{
ArgumentNullException.ThrowIfNull(cut);
- if (cut.Played is { } played)
+ if (cut.Played < TimeSpan.Zero)
{
- ArgumentOutOfRangeException.ThrowIfLessThan(played, TimeSpan.Zero, nameof(cut));
+ throw new ArgumentOutOfRangeException(nameof(cut), cut.Played, "The played time is negative.");
}
lock (_session.TurnLock)
@@ -68,9 +68,9 @@ internal bool Cut(int turnIndex, TurnCut cut)
internal bool Recut(int turnIndex, TurnCut cut)
{
ArgumentNullException.ThrowIfNull(cut);
- if (cut.Played is { } played)
+ if (cut.Played < TimeSpan.Zero)
{
- ArgumentOutOfRangeException.ThrowIfLessThan(played, TimeSpan.Zero, nameof(cut));
+ throw new ArgumentOutOfRangeException(nameof(cut), cut.Played, "The played time is negative.");
}
lock (_session.TurnLock)
diff --git a/src/AgentCore.Application/Runtime/Harness/BackgroundSessionRelease.cs b/src/AgentCore.Application/Runtime/Harness/BackgroundSessionRelease.cs
index 76e48fed..326fcd55 100644
--- a/src/AgentCore.Application/Runtime/Harness/BackgroundSessionRelease.cs
+++ b/src/AgentCore.Application/Runtime/Harness/BackgroundSessionRelease.cs
@@ -53,7 +53,7 @@ public static async ValueTask ReleaseAsync(
try
{
long started = Stopwatch.GetTimestamp();
-
+
await provider.ReleaseSessionAsync(session, cancelRunning: true, ReleaseTimeout, cancellationToken)
.ConfigureAwait(false);
diff --git a/src/AgentCore.Application/Runtime/Session/ChannelCommands.cs b/src/AgentCore.Application/Runtime/Session/ChannelCommands.cs
index ff69309a..15eb9866 100644
--- a/src/AgentCore.Application/Runtime/Session/ChannelCommands.cs
+++ b/src/AgentCore.Application/Runtime/Session/ChannelCommands.cs
@@ -21,7 +21,7 @@ internal void Attach(IConversationChannel channel)
public ChannelCommandResult Send(ChannelCommand command)
{
ArgumentNullException.ThrowIfNull(command);
-
+
if (session.IsComplete || session.Lifetime.Ending.Requested)
{
return ChannelCommandResult.Ending;
diff --git a/src/AgentCore.Application/Runtime/Session/ConversationBusyMark.cs b/src/AgentCore.Application/Runtime/Session/ConversationBusyMark.cs
index 14f2a963..03318827 100644
--- a/src/AgentCore.Application/Runtime/Session/ConversationBusyMark.cs
+++ b/src/AgentCore.Application/Runtime/Session/ConversationBusyMark.cs
@@ -109,7 +109,7 @@ private async ValueTask HoldAsync(long started, CancellationToken cancellationTo
internal async ValueTask EnterRequestAsync(long started, CancellationToken cancellationToken)
{
TimeSpan left = WaitLimit - _session.Time.GetElapsedTime(started);
-
+
if (left <= TimeSpan.Zero)
{
throw Refuse(_session);
diff --git a/src/AgentCore.Application/Runtime/Session/ConversationEnding.cs b/src/AgentCore.Application/Runtime/Session/ConversationEnding.cs
index 635fa158..18291a5c 100644
--- a/src/AgentCore.Application/Runtime/Session/ConversationEnding.cs
+++ b/src/AgentCore.Application/Runtime/Session/ConversationEnding.cs
@@ -100,6 +100,7 @@ private void StopTools()
}
catch (ObjectDisposedException)
{
+ // The session is disposed, so no tool is left to stop.
}
finally
{
diff --git a/src/AgentCore.Application/Runtime/Session/ConversationStoreReads.cs b/src/AgentCore.Application/Runtime/Session/ConversationStoreReads.cs
index 0eb67d4d..bf40e60b 100644
--- a/src/AgentCore.Application/Runtime/Session/ConversationStoreReads.cs
+++ b/src/AgentCore.Application/Runtime/Session/ConversationStoreReads.cs
@@ -1,6 +1,4 @@
using AgentCore.Application.Conversation;
-using AgentCore.Application.Diagnostics;
-using AgentCore.Application.Hooks;
using AgentCore.Application.Hooks.Notices;
using AgentCore.Application.Transcript;
using Microsoft.Agents.AI;
@@ -158,10 +156,9 @@ private async ValueTask OvertakenAsync(
/// Whether the store holds every one of these rows, including a row a newer summary now stands in for.
private async ValueTask LandedAsync(IReadOnlyList held, HashSet read, CancellationToken cancellationToken)
{
- foreach (string messageId in held)
+ foreach (string messageId in held.Where(id => !read.Contains(id)))
{
- if (!read.Contains(messageId)
- && await _session.Compiled.ConversationStore.OrdinalOfAsync(_session.ConversationId, messageId, cancellationToken).ConfigureAwait(false) is null)
+ if (await _session.Compiled.ConversationStore.OrdinalOfAsync(_session.ConversationId, messageId, cancellationToken).ConfigureAwait(false) is null)
{
return false;
}
diff --git a/src/AgentCore.Application/Runtime/ToolCalls/AuditingFunctionInvokingChatClient.cs b/src/AgentCore.Application/Runtime/ToolCalls/AuditingFunctionInvokingChatClient.cs
index 431b3587..af21bfdf 100644
--- a/src/AgentCore.Application/Runtime/ToolCalls/AuditingFunctionInvokingChatClient.cs
+++ b/src/AgentCore.Application/Runtime/ToolCalls/AuditingFunctionInvokingChatClient.cs
@@ -6,7 +6,6 @@
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using AgentCore.Application.Runtime.Cut;
-using AgentCore.Application.Runtime.Session;
namespace AgentCore.Application.Runtime.ToolCalls
{
@@ -99,7 +98,11 @@ internal void Release(IReadOnlyList callIds)
return await run.Running.ConfigureAwait(false);
}
- IEnumerable Attachments() => drain?.TakeFor(callId) ?? [];
+ IEnumerable Attachments()
+ {
+ return drain?.TakeFor(callId) ?? [];
+ }
+
object? result;
try
{
diff --git a/src/AgentCore.Application/Runtime/ToolCalls/ConversationToolRuns.cs b/src/AgentCore.Application/Runtime/ToolCalls/ConversationToolRuns.cs
index 5e0f427d..cc695c05 100644
--- a/src/AgentCore.Application/Runtime/ToolCalls/ConversationToolRuns.cs
+++ b/src/AgentCore.Application/Runtime/ToolCalls/ConversationToolRuns.cs
@@ -112,7 +112,11 @@ internal void Returned(ToolRun run, object? result, Func>
ArgumentNullException.ThrowIfNull(attachments);
ArgumentNullException.ThrowIfNull(turn);
- IReadOnlyList Pair() => PairOf(run.Call, result, attachments());
+ IReadOnlyList Pair()
+ {
+ return PairOf(run.Call, result, attachments());
+ }
+
bool now;
lock (_gate)
{
@@ -203,11 +207,13 @@ internal Task WhenCarriedDone()
}
}
- private static IReadOnlyList PairOf(FunctionCallContent call, object? result, IEnumerable attachments) =>
- [
+ private static IReadOnlyList PairOf(FunctionCallContent call, object? result, IEnumerable attachments)
+ {
+ return [
new ChatMessage(ChatRole.Assistant, [new FunctionCallContent(call.CallId, call.Name, call.Arguments)]),
new ChatMessage(ChatRole.Tool, [new FunctionResultContent(call.CallId, result), .. attachments]),
];
+ }
private void CarriedOut()
{
diff --git a/src/AgentCore.Application/Runtime/Turn/ConversationTurn.cs b/src/AgentCore.Application/Runtime/Turn/ConversationTurn.cs
index 1fedc51a..cc12f83b 100644
--- a/src/AgentCore.Application/Runtime/Turn/ConversationTurn.cs
+++ b/src/AgentCore.Application/Runtime/Turn/ConversationTurn.cs
@@ -2,7 +2,6 @@
using AgentCore.Domain.Knowledge;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
-using AgentCore.Application.Runtime.Turn.Lifecycle;
namespace AgentCore.Application.Runtime.Turn
{
diff --git a/src/AgentCore.Application/Runtime/Turn/Lifecycle/ConversationTurnCommit.cs b/src/AgentCore.Application/Runtime/Turn/Lifecycle/ConversationTurnCommit.cs
index 304819a5..0919ee02 100644
--- a/src/AgentCore.Application/Runtime/Turn/Lifecycle/ConversationTurnCommit.cs
+++ b/src/AgentCore.Application/Runtime/Turn/Lifecycle/ConversationTurnCommit.cs
@@ -1,4 +1,3 @@
-using AgentCore.Application.Diagnostics;
using AgentCore.Application.Hooks.Engine;
using AgentCore.Application.Hooks.Notices;
using AgentCore.Domain;
diff --git a/src/AgentCore.Application/Runtime/Turn/Lifecycle/ConversationTurnCompletion.cs b/src/AgentCore.Application/Runtime/Turn/Lifecycle/ConversationTurnCompletion.cs
index 4d0e545c..56fe6195 100644
--- a/src/AgentCore.Application/Runtime/Turn/Lifecycle/ConversationTurnCompletion.cs
+++ b/src/AgentCore.Application/Runtime/Turn/Lifecycle/ConversationTurnCompletion.cs
@@ -1,5 +1,4 @@
using System.Diagnostics;
-using System.Globalization;
using AgentCore.Application.Diagnostics;
using AgentCore.Application.Hooks.BuiltIn;
using AgentCore.Application.Hooks.Engine;
diff --git a/src/AgentCore.Application/Runtime/Turn/Lifecycle/ConversationTurnStream.cs b/src/AgentCore.Application/Runtime/Turn/Lifecycle/ConversationTurnStream.cs
index 0ccb65b6..0ffbc042 100644
--- a/src/AgentCore.Application/Runtime/Turn/Lifecycle/ConversationTurnStream.cs
+++ b/src/AgentCore.Application/Runtime/Turn/Lifecycle/ConversationTurnStream.cs
@@ -54,6 +54,7 @@ internal async Task RunTurnCoreAsync(
await foreach (ChatResponseUpdate _ in RunTurnStreamingIteratorAsync(userInput, origin, result, cancellationToken)
.ConfigureAwait(false))
{
+ // Only the result box matters here; the updates are read so the turn runs to its end.
}
return result.Value!;
@@ -229,9 +230,12 @@ private async IAsyncEnumerable RunAsync(
await stream.DisposeAsync().ConfigureAwait(false);
}
- result?.Value = completion.Result
- ?? throw new InvalidOperationException(
- $"The turn {turn.Index} of the conversation '{_session.ConversationId}' ended without a seal.");
+ if (result is not null)
+ {
+ result.Value = completion.Result
+ ?? throw new InvalidOperationException(
+ $"The turn {turn.Index} of the conversation '{_session.ConversationId}' ended without a seal.");
+ }
}
finally
{
diff --git a/src/AgentCore.Application/Runtime/Turn/Lifecycle/TurnDisposition.cs b/src/AgentCore.Application/Runtime/Turn/Lifecycle/TurnDisposition.cs
index 4ae17bab..0121417f 100644
--- a/src/AgentCore.Application/Runtime/Turn/Lifecycle/TurnDisposition.cs
+++ b/src/AgentCore.Application/Runtime/Turn/Lifecycle/TurnDisposition.cs
@@ -1,4 +1,3 @@
-using Microsoft.Agents.AI;
using AgentCore.Application.Runtime.Agents;
using AgentCore.Application.Runtime.Session;
diff --git a/src/AgentCore.Application/Runtime/Turn/Lifecycle/TurnReplyResolution.cs b/src/AgentCore.Application/Runtime/Turn/Lifecycle/TurnReplyResolution.cs
index 447c6161..38e01b12 100644
--- a/src/AgentCore.Application/Runtime/Turn/Lifecycle/TurnReplyResolution.cs
+++ b/src/AgentCore.Application/Runtime/Turn/Lifecycle/TurnReplyResolution.cs
@@ -1,6 +1,4 @@
using System.Text.Json;
-using AgentCore.Application.Diagnostics;
-using AgentCore.Application.Hooks;
using AgentCore.Application.Hooks.Notices;
using AgentCore.Domain;
using Microsoft.Agents.AI;
diff --git a/src/AgentCore.Application/Runtime/Turn/TurnMessages.cs b/src/AgentCore.Application/Runtime/Turn/TurnMessages.cs
index 7538513a..21b8826b 100644
--- a/src/AgentCore.Application/Runtime/Turn/TurnMessages.cs
+++ b/src/AgentCore.Application/Runtime/Turn/TurnMessages.cs
@@ -88,7 +88,7 @@ internal static List FinishedMessages(IList messages)
];
if (!tools.Any(content => content is FunctionCallContent or FunctionResultContent or ToolApprovalRequestContent
- || content is TextContent { Text.Length: > 0 }))
+ or TextContent { Text.Length: > 0 }))
{
// A message whose every call is still in flight, with no words beside them. It does not
// belong in the next turn.
diff --git a/src/AgentCore.Application/Runtime/Turn/TurnUpdateMerge.cs b/src/AgentCore.Application/Runtime/Turn/TurnUpdateMerge.cs
index 07e66dfb..f704d682 100644
--- a/src/AgentCore.Application/Runtime/Turn/TurnUpdateMerge.cs
+++ b/src/AgentCore.Application/Runtime/Turn/TurnUpdateMerge.cs
@@ -14,14 +14,22 @@ internal static class TurnUpdateMerge
/// The run's own stream. This owns and disposes its enumerator.
/// This turn's notice channel. Completed once ends or fails.
/// Cancels the run.
- internal static async IAsyncEnumerable RunAsync(
+ internal static IAsyncEnumerable RunAsync(
IAsyncEnumerable inner,
TurnNotices notices,
- [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(inner);
ArgumentNullException.ThrowIfNull(notices);
+ return MergeAsync(inner, notices, cancellationToken);
+ }
+
+ private static async IAsyncEnumerable MergeAsync(
+ IAsyncEnumerable inner,
+ TurnNotices notices,
+ [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
ChannelReader reader = notices.Reader;
await using IAsyncEnumerator e = inner.GetAsyncEnumerator(cancellationToken);
Task? pendingMove = null;
@@ -83,10 +91,11 @@ internal static async IAsyncEnumerable RunAsync(
// already unwinding on the same token, so it is settled here first.
try
{
- await pendingMove.ConfigureAwait(false);
+ _ = await pendingMove.ConfigureAwait(false);
}
catch
{
+ // The run's own failure or cancellation already reaches the caller; this only lets the enumerator dispose.
}
}
}
diff --git a/src/AgentCore.Application/Sessions/Memory/InMemoryConversationSessions.cs b/src/AgentCore.Application/Sessions/Memory/InMemoryConversationSessions.cs
index 13dc6442..cba62b28 100644
--- a/src/AgentCore.Application/Sessions/Memory/InMemoryConversationSessions.cs
+++ b/src/AgentCore.Application/Sessions/Memory/InMemoryConversationSessions.cs
@@ -165,7 +165,7 @@ public async ValueTask CloseAsync(string entry, string conversationId, Cancellat
/// is empty.
internal void SweepWorkspaceRoot(string root, ILogger? logger)
{
- new WorkspaceRootSweeper(_idleTimeout, _time).Sweep(root, id => _slots.ContainsKey(id), logger);
+ new WorkspaceRootSweeper(_idleTimeout, _time).Sweep(root, _slots.ContainsKey, logger);
}
/// Stops every idle timer. A build or a close already running finishes, and wakes its waiters.
diff --git a/src/AgentCore.Application/Sessions/Memory/WorkspaceRootSweeper.cs b/src/AgentCore.Application/Sessions/Memory/WorkspaceRootSweeper.cs
index 5dde0b26..e0728b87 100644
--- a/src/AgentCore.Application/Sessions/Memory/WorkspaceRootSweeper.cs
+++ b/src/AgentCore.Application/Sessions/Memory/WorkspaceRootSweeper.cs
@@ -68,11 +68,11 @@ internal void Sweep(string root, Func isLive, ILogger? logger)
// No margin for clock skew: a root shared by several servers is the host's to coordinate.
DateTime cutoff = _time.GetUtcNow().UtcDateTime - _idleTimeout;
- HashSet liveFolderNames = new(folders.Select(folder => Path.GetFileName(folder)!), StringComparer.Ordinal);
+ HashSet liveFolderNames = new(folders.Select(folder => Path.GetFileName(folder)), StringComparer.Ordinal);
foreach (string folder in folders)
{
- if (isLive(Path.GetFileName(folder)!))
+ if (isLive(Path.GetFileName(folder)))
{
continue;
}
diff --git a/src/AgentCore.Application/Tools/Builtin/FilePublishTool.cs b/src/AgentCore.Application/Tools/Builtin/FilePublishTool.cs
index bb867557..24bc92c8 100644
--- a/src/AgentCore.Application/Tools/Builtin/FilePublishTool.cs
+++ b/src/AgentCore.Application/Tools/Builtin/FilePublishTool.cs
@@ -16,7 +16,8 @@ namespace AgentCore.Application.Tools.Builtin
{
///
/// Copies one file out of the running conversation's workspace into the blob store, owned by the conversation, and
- /// files a on the turn so the card reaches the person.
+ /// files a on the turn so the card reaches the person. The model gets a stable
+ /// sandbox:/ name to link the file by.
///
internal sealed class FilePublishTool
{
@@ -104,7 +105,6 @@ private async ValueTask PublishAsync(
}
BlobRef blob;
- Uri? url;
try
{
@@ -113,8 +113,6 @@ private async ValueTask PublishAsync(
blob = await _blobs
.PutAsync(new BlobWrite(conversationId, name, BlobMediaTypes.Of(name), content, length), cancellationToken)
.ConfigureAwait(false);
-
- url = await _blobs.LinkAsync(blob, BlobLink.Lifetime, cancellationToken).ConfigureAwait(false);
}
catch (Exception exception) when (exception is not OperationCanceledException)
{
@@ -143,7 +141,7 @@ private async ValueTask PublishAsync(
["name"] = blob.Name,
["mediaType"] = blob.MediaType,
["length"] = blob.Length,
- ["url"] = url?.ToString(),
+ ["link"] = SandboxLink(blob.Name),
};
}
@@ -184,6 +182,12 @@ private async ValueTask PublishAsync(
: null;
}
+ /// The stable name the model writes into its reply: sandbox:/ and the stored name, escaped so markdown and a URL decoder read it back whole.
+ private static string SandboxLink(string name)
+ {
+ return "sandbox:/" + Uri.EscapeDataString(name);
+ }
+
private JsonObject Failed(string message)
{
return ToolErrorResult.Create(_tool.Id, message);
diff --git a/src/AgentCore.Application/Tools/Builtin/FilePublishToolDefinition.cs b/src/AgentCore.Application/Tools/Builtin/FilePublishToolDefinition.cs
index b40bbcd0..225b0aaf 100644
--- a/src/AgentCore.Application/Tools/Builtin/FilePublishToolDefinition.cs
+++ b/src/AgentCore.Application/Tools/Builtin/FilePublishToolDefinition.cs
@@ -17,8 +17,11 @@ internal sealed class FilePublishToolDefinition : IBuiltinToolDefinition
///
public string DefaultDescription =>
- "Hands one file from the workspace to the person and returns a link to it. "
- + "A file that is not published is lost when the conversation ends.";
+ "Hands one file from the workspace to the person. "
+ + "A file that is not published is lost when the conversation ends. "
+ + "The result has a `link`. Link the file in your reply like [Sales by month](sandbox:/sales.csv), "
+ + "copying the `link` value exactly, with no angle brackets or other changes. Never write any other address for the file. "
+ + "Give each file a distinct name: a second file with the same name replaces the first.";
///
public AITool Build(ToolConfiguration tool, BuiltinToolPorts ports)
diff --git a/src/AgentCore.Application/Transcript/FrontVoice.cs b/src/AgentCore.Application/Transcript/FrontVoice.cs
index c8b2ab52..6418633b 100644
--- a/src/AgentCore.Application/Transcript/FrontVoice.cs
+++ b/src/AgentCore.Application/Transcript/FrontVoice.cs
@@ -13,10 +13,15 @@ internal static class FrontVoice
internal const string AuthorName = "front_voice";
/// Builds one front-voice line.
- internal static ChatMessage Line(string text) => new(ChatRole.Assistant, text) { AuthorName = AuthorName };
+ internal static ChatMessage Line(string text)
+ {
+ return new(ChatRole.Assistant, text) { AuthorName = AuthorName };
+ }
/// Whether a message is the agent's own reply: an assistant message no front voice spoke.
- internal static bool IsAgentReply(ChatMessage message) =>
- message.Role == ChatRole.Assistant && !string.Equals(message.AuthorName, AuthorName, StringComparison.Ordinal);
+ internal static bool IsAgentReply(ChatMessage message)
+ {
+ return message.Role == ChatRole.Assistant && !string.Equals(message.AuthorName, AuthorName, StringComparison.Ordinal);
+ }
}
}
diff --git a/src/AgentCore.AspNetCore/Calls/CallAnswer.cs b/src/AgentCore.AspNetCore/Calls/CallAnswer.cs
index 9861b752..dce59e8e 100644
--- a/src/AgentCore.AspNetCore/Calls/CallAnswer.cs
+++ b/src/AgentCore.AspNetCore/Calls/CallAnswer.cs
@@ -20,9 +20,15 @@ internal sealed record CallAnswer(CallAnswerKind Kind, string Text)
{
internal static CallAnswer Withdrawn { get; } = new(CallAnswerKind.Withdrawn, string.Empty);
- internal static CallAnswer Answered(string text) => new(CallAnswerKind.Answered, text);
+ internal static CallAnswer Answered(string text)
+ {
+ return new(CallAnswerKind.Answered, text);
+ }
/// The answer of an ask that could not run: the vendor speaks .
- internal static CallAnswer FallbackOf(string fallbackReply) => new(CallAnswerKind.Fallback, fallbackReply);
+ internal static CallAnswer FallbackOf(string fallbackReply)
+ {
+ return new(CallAnswerKind.Fallback, fallbackReply);
+ }
}
}
diff --git a/src/AgentCore.AspNetCore/Calls/CallAsks.cs b/src/AgentCore.AspNetCore/Calls/CallAsks.cs
index 98269bb7..2b7a3093 100644
--- a/src/AgentCore.AspNetCore/Calls/CallAsks.cs
+++ b/src/AgentCore.AspNetCore/Calls/CallAsks.cs
@@ -137,7 +137,10 @@ private CallAnswer Failed(CallAsk mine, Exception fault)
}
// The engine's own fallback for this entry: the entry's fallbackReply, else the document's.
- private CallAnswer Fallback() => CallAnswer.FallbackOf(call.Session.Compiled.FallbackReply);
+ private CallAnswer Fallback()
+ {
+ return CallAnswer.FallbackOf(call.Session.Compiled.FallbackReply);
+ }
// Every ask hangs off the last message the caller heard an answer to, so a resend on the same
// parent withdraws an unanswered ask, whether or not its turn filed a row yet. The first anchor is the last
diff --git a/src/AgentCore.AspNetCore/Calls/PhoneCallAdmission.cs b/src/AgentCore.AspNetCore/Calls/PhoneCallAdmission.cs
index a01eb10b..39e50651 100644
--- a/src/AgentCore.AspNetCore/Calls/PhoneCallAdmission.cs
+++ b/src/AgentCore.AspNetCore/Calls/PhoneCallAdmission.cs
@@ -13,10 +13,19 @@ internal sealed record PhoneCallAdmission(PhoneCall? Call, CallRefusal? Refusal)
/// Gets whether the call was refused as busy because another call holds its conversation.
internal bool HeldByCall { get; private init; }
- internal static PhoneCallAdmission Admitted(PhoneCall call) => new(call, null);
+ internal static PhoneCallAdmission Admitted(PhoneCall call)
+ {
+ return new(call, null);
+ }
- internal static PhoneCallAdmission Refused(CallRefusal refusal) => new(null, refusal);
+ internal static PhoneCallAdmission Refused(CallRefusal refusal)
+ {
+ return new(null, refusal);
+ }
- internal static PhoneCallAdmission RefusedHeldByCall() => new(null, CallRefusal.Busy) { HeldByCall = true };
+ internal static PhoneCallAdmission RefusedHeldByCall()
+ {
+ return new(null, CallRefusal.Busy) { HeldByCall = true };
+ }
}
}
diff --git a/src/AgentCore.AspNetCore/DependencyInjection/AgentCoreBoot.cs b/src/AgentCore.AspNetCore/DependencyInjection/AgentCoreBoot.cs
index d5b4cbf2..b10c22b6 100644
--- a/src/AgentCore.AspNetCore/DependencyInjection/AgentCoreBoot.cs
+++ b/src/AgentCore.AspNetCore/DependencyInjection/AgentCoreBoot.cs
@@ -17,7 +17,6 @@
using AgentCore.Application.Tools.Registry;
using AgentCore.AspNetCore.DependencyInjection.Startup;
using AgentCore.AspNetCore.Voice.Ports;
-using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -36,7 +35,7 @@ internal sealed class AgentCoreBoot : IAsyncDisposable, IDisposable
private readonly List