Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

17 changes: 0 additions & 17 deletions src/AgentCore.Application/Conversation/Actions/ICallTransfer.cs

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace AgentCore.Application.Conversation.Commands
{
/// <summary>
/// A fact for a voice that speaks for itself, for example a note a host looked up after the call was answered.
/// </summary>
public sealed record AddVoiceContextCommand : ChannelCommand
{
private const int MaxLength = 1000;

/// <summary>Keeps the fact, cut to its first 1,000 characters.</summary>
/// <param name="text">The fact, as the voice should read it.</param>
/// <exception cref="ArgumentException"><paramref name="text"/> is <see langword="null"/>, empty, or white space.</exception>
public AddVoiceContextCommand(string text)
{
ArgumentException.ThrowIfNullOrWhiteSpace(text);
Text = text.Length > MaxLength ? text[..MaxLength] : text;
}

/// <summary>Gets the fact.</summary>
public string Text { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace AgentCore.Application.Conversation.Commands
{
/// <summary>
/// How a host's transfer went, as its <see cref="IChannelCommandHandler{TCommand, TOutcome}"/> answers it. A channel
/// reads every value it does not know as <see cref="NotTaken"/>.
/// </summary>
public enum CallTransferOutcome
{
/// <summary>The line did not take the call; the caller is still on this call.</summary>
NotTaken = 0,

/// <summary>The line took the call; this side hangs its leg up.</summary>
Taken = 1,
}
}
12 changes: 12 additions & 0 deletions src/AgentCore.Application/Conversation/Commands/ChannelCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace AgentCore.Application.Conversation.Commands
{
/// <summary>
/// The base of every command a conversation's live channel.
/// </summary>
public abstract record ChannelCommand
{
private protected ChannelCommand()
{
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
namespace AgentCore.Application.Conversation.Actions
namespace AgentCore.Application.Conversation.Commands
{
/// <summary>One transfer an <see cref="ICallTransfer"/> carries out.</summary>
public sealed record CallTransfer
/// <summary>The conversation an <see cref="IChannelCommandHandler{TCommand, TOutcome}"/> acts on.</summary>
public sealed record ChannelCommandContext
{
/// <summary>Gets the conversation the call carries.</summary>
/// <summary>Gets the conversation the channel carries.</summary>
public required string ConversationId { get; init; }

/// <summary>Gets the line, as the <see cref="TransferAction"/> named it.</summary>
public required Uri Target { get; init; }

/// <summary>
/// Gets the call's own transport headers, such as SIP headers, by name, case-insensitive. A host finds its phone
/// system's id for the call here when the system that placed the call put it in.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace AgentCore.Application.Conversation.Commands
{
/// <summary>
/// The answer to a command.
/// </summary>
public enum ChannelCommandResult
{
/// <summary>Accepted. It runs when its command says, such as after the current answer was delivered.</summary>
Scheduled = 0,

/// <summary>The channel has no way to do it, such as a web chat asked to transfer a call. The tool picks the fallback.</summary>
NotSupported = 1,

/// <summary>Refused: an end was already asked for, and a later command would contradict it.</summary>
Ending = 2,
}
}
10 changes: 10 additions & 0 deletions src/AgentCore.Application/Conversation/Commands/EndCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using AgentCore.Domain.Audit;

namespace AgentCore.Application.Conversation.Commands
{
/// <summary>
/// The same end as <c>ConversationSession.EndConversation</c>, as a command.
/// </summary>
/// <param name="Reason">Stored as the <c>conversation.ended</c> token, so it becomes part of the audit record.</param>
public sealed record EndCommand(ConversationEndReason Reason) : ChannelCommand;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace AgentCore.Application.Conversation.Commands
{
/// <summary>
/// A host that carries out one command itself.
/// </summary>
/// <typeparam name="TCommand">The command the host carries out.</typeparam>
/// <typeparam name="TOutcome">What the channel needs to know of how it went, such as <see cref="CallTransferOutcome"/>.</typeparam>
public interface IChannelCommandHandler<in TCommand, TOutcome>
where TCommand : ChannelCommand
{
/// <summary>Carries out the command. A transfer runs once the caller heard the answer that asked for it.</summary>
/// <param name="command">The command, as the tool sent it.</param>
/// <param name="context">The conversation the command acts on.</param>
/// <param name="cancellationToken">Cancels the command.</param>
/// <returns>How it went.</returns>
Task<TOutcome> HandleAsync(TCommand command, ChannelCommandContext context, CancellationToken cancellationToken);
}
}
14 changes: 14 additions & 0 deletions src/AgentCore.Application/Conversation/Commands/IChannelControl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace AgentCore.Application.Conversation.Commands
{
/// <summary>
/// One door for every command.
/// </summary>
public interface IChannelControl
{
/// <summary>Returns at once and runs nothing inline: each command runs when it says, such as after the current answer was delivered.</summary>
/// <param name="command">One of the commands AgentCore defines.</param>
/// <returns>Whether the command will run, and if not, why, so the tool can tell the model.</returns>
/// <exception cref="ArgumentNullException"><paramref name="command"/> is <see langword="null"/>.</exception>
ChannelCommandResult Send(ChannelCommand command);
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
namespace AgentCore.Application.Conversation.Actions
namespace AgentCore.Application.Conversation.Commands
{
/// <summary>
/// Hands a phone call to another line. It waits until the caller heard the current answer, because a transfer that
/// cuts the reply loses what the caller was just told.
/// Hands a phone call to another line.
/// </summary>
public sealed record TransferAction : ConversationAction
public sealed record TransferCommand : ChannelCommand
{
/// <summary>Checks the target here, so a bad target fails in the tool that made it and not later on the call.</summary>
/// <param name="target">A <c>tel:</c>, <c>sip:</c> or <c>sips:</c> URI, the forms a SIP REFER can carry.</param>
/// <exception cref="ArgumentNullException"><paramref name="target"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="target"/> is not an absolute tel, sip or sips URI.</exception>
public TransferAction(Uri target)
public TransferCommand(Uri target)
{
ArgumentNullException.ThrowIfNull(target);
if (!target.IsAbsoluteUri || target.Scheme is not ("tel" or "sip" or "sips"))
Expand All @@ -25,10 +24,7 @@ public TransferAction(Uri target)
public Uri Target { get; }

/// <summary>
/// Gets the instruction the voice follows when the line did not take the call, after which the call goes on. The
/// host writes it, because AgentCore writes no text for the model. Without it a failed transfer hangs up. A voice
/// may keep it for the rest of the call (GPT-Live appends it to its session instructions, as it does the
/// greeting), so word it as one past event, such as "the transfer you just started did not go through".
/// Gets the instruction the voice follows when the line did not take the call, after which the call goes on.
/// </summary>
public string? IfFailed { get; init; }
}
Expand Down
1 change: 0 additions & 1 deletion src/AgentCore.Application/Hooks/BuiltIn/CallBriefHook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ namespace AgentCore.Application.Hooks.BuiltIn
{
/// <summary>
/// Adds an accepted call's brief to every run of the call's conversation, as instructions.
/// AgentCore never reads, wraps, or changes the brief; the host writes all of it.
/// </summary>
internal sealed class CallBriefHook : AgentHook
{
Expand Down
1 change: 1 addition & 0 deletions src/AgentCore.Application/Hooks/Gates/CallGate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public void Accept(string conversationId, string? brief = null)
ArgumentException.ThrowIfNullOrEmpty(conversationId);

string? kept = brief is { Length: > MaxBriefLength } ? brief[..MaxBriefLength] : brief;

Stage(terminal: true, () =>
{
AcceptedConversationId = conversationId;
Expand Down
Loading
Loading