From 69707f32a7b310935dded8dbeb1af45949ce7b27 Mon Sep 17 00:00:00 2001 From: Jacob Dubin Date: Wed, 6 May 2026 20:57:32 -0500 Subject: [PATCH] Update commit message --- .../Services/JiboInteractionService.cs | 11 ++- .../ResponsePlanToSocketMessagesMapper.cs | 74 +++++++++++++++---- .../Models/WebSocketTurnState.cs | 2 +- .../WebSockets/JiboWebSocketServiceTests.cs | 16 +++- 4 files changed, 84 insertions(+), 19 deletions(-) diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs index 1b393e9..011de76 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs @@ -386,7 +386,16 @@ public sealed class JiboInteractionService( { return new JiboInteractionDecision( "proactive_offer_pizza_fact", - "Do you want to hear a fun pizza fact?"); + "Do you want to hear a fun pizza fact?", + "chitchat-skill", + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["mim_id"] = "runtime-chat", + ["mim_type"] = "question", + ["prompt_id"] = "RUNTIME_PROMPT", + ["prompt_sub_category"] = "Q", + ["listen_contexts"] = new[] { "shared/yes_no" } + }); } private static JiboInteractionDecision BuildProactivePizzaFactDecision() diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/ResponsePlanToSocketMessagesMapper.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/ResponsePlanToSocketMessagesMapper.cs index 5f196a2..7a9d030 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/ResponsePlanToSocketMessagesMapper.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/ResponsePlanToSocketMessagesMapper.cs @@ -101,7 +101,7 @@ public sealed class ResponsePlanToSocketMessagesMapper ? clientIntent : transcript; var outboundRules = isProactivePizzaFactOffer - ? ["shared/yes_no", "$YESNO"] + ? ["shared/yes_no"] : isWordOfDayLaunch ? ["word-of-the-day/menu"] : isGlobalCommand @@ -794,6 +794,31 @@ public sealed class ResponsePlanToSocketMessagesMapper var mimType = ReadPayloadString(skillPayload, "mim_type") ?? "announcement"; var promptId = ReadPayloadString(skillPayload, "prompt_id") ?? "RUNTIME_PROMPT"; var promptSubCategory = ReadPayloadString(skillPayload, "prompt_sub_category") ?? "AN"; + var listenContexts = ReadPayloadStringArray(skillPayload, "listen_contexts"); + var jcpConfig = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["play"] = new + { + esml, + meta = new + { + prompt_id = promptId, + prompt_sub_category = promptSubCategory, + mim_id = mimId, + mim_type = mimType + } + } + }; + + if (listenContexts.Count > 0) + { + jcpConfig["listen"] = new + { + id = CreateProtocolId(), + type = "LISTEN", + contexts = listenContexts + }; + } return new { @@ -814,20 +839,7 @@ public sealed class ResponsePlanToSocketMessagesMapper jcp = new { type = "SLIM", - config = new - { - play = new - { - esml, - meta = new - { - prompt_id = promptId, - prompt_sub_category = promptSubCategory, - mim_id = mimId, - mim_type = mimType - } - } - } + config = jcpConfig } } }, @@ -1031,10 +1043,42 @@ public sealed class ResponsePlanToSocketMessagesMapper return value?.ToString(); } + private static IReadOnlyList ReadPayloadStringArray(IDictionary? payload, string key) + { + if (payload is null || !payload.TryGetValue(key, out var value) || value is null) + { + return []; + } + + return value switch + { + string text => [.. text + .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Where(static context => !string.IsNullOrWhiteSpace(context))], + string[] contexts => [.. contexts.Where(static context => !string.IsNullOrWhiteSpace(context))], + IEnumerable contexts => [.. contexts.Where(static context => !string.IsNullOrWhiteSpace(context))], + JsonElement jsonElement when jsonElement.ValueKind == JsonValueKind.Array => [.. jsonElement + .EnumerateArray() + .Select(static item => item.GetString()) + .Where(static context => !string.IsNullOrWhiteSpace(context)) + .Select(static context => context!)], + IEnumerable contexts => [.. contexts + .Select(static context => context?.ToString()) + .Where(static context => !string.IsNullOrWhiteSpace(context)) + .Select(static context => context!)], + _ => string.IsNullOrWhiteSpace(value.ToString()) ? [] : [value.ToString()!] + }; + } + private static string CreateHubMessageId() { return $"mid-{Guid.NewGuid()}"; } + private static string CreateProtocolId() + { + return Guid.NewGuid().ToString("N"); + } + public sealed record SocketReplyPlan(string Text, int DelayMs = 0); } diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Domain/Models/WebSocketTurnState.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Domain/Models/WebSocketTurnState.cs index 61bc841..4585a35 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Domain/Models/WebSocketTurnState.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Domain/Models/WebSocketTurnState.cs @@ -3,7 +3,7 @@ namespace Jibo.Cloud.Domain.Models; public sealed class WebSocketTurnState { public static readonly TimeSpan DefaultLateAudioIgnoreWindow = TimeSpan.FromSeconds(2); - public static readonly TimeSpan DiagnosticSpeechLateAudioIgnoreWindow = TimeSpan.FromSeconds(8); + public static readonly TimeSpan DiagnosticSpeechLateAudioIgnoreWindow = TimeSpan.FromSeconds(4); public string? TransId { get; set; } public string? ContextPayload { get; set; } diff --git a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs index 7ab7779..8edd903 100644 --- a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs +++ b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs @@ -116,7 +116,7 @@ public sealed class JiboWebSocketServiceTests Assert.False(session.FollowUpOpen); Assert.False(session.TurnState.AwaitingTurnCompletion); Assert.False(session.TurnState.SawListen); - Assert.True(session.TurnState.IgnoreAdditionalAudioUntilUtc > DateTimeOffset.UtcNow.AddSeconds(6)); + Assert.True(session.TurnState.IgnoreAdditionalAudioUntilUtc > DateTimeOffset.UtcNow.AddSeconds(3)); var tailListenReplies = await _service.HandleMessageAsync(new WebSocketMessageEnvelope { @@ -3225,9 +3225,21 @@ public sealed class JiboWebSocketServiceTests { Assert.Equal("proactive_offer_pizza_fact", offerListenPayload.RootElement.GetProperty("data").GetProperty("nlu").GetProperty("intent").GetString()); Assert.Equal("shared/yes_no", offerListenPayload.RootElement.GetProperty("data").GetProperty("nlu").GetProperty("rules")[0].GetString()); - Assert.Equal("$YESNO", offerListenPayload.RootElement.GetProperty("data").GetProperty("nlu").GetProperty("rules")[1].GetString()); Assert.Equal("shared/yes_no", offerListenPayload.RootElement.GetProperty("data").GetProperty("match").GetProperty("rule").GetString()); } + using (var offerSkillPayload = JsonDocument.Parse(offerReplies[2].Text!)) + { + var jcpConfig = offerSkillPayload.RootElement + .GetProperty("data") + .GetProperty("action") + .GetProperty("config") + .GetProperty("jcp") + .GetProperty("config"); + Assert.Equal("Q", jcpConfig.GetProperty("play").GetProperty("meta").GetProperty("prompt_sub_category").GetString()); + Assert.Equal("question", jcpConfig.GetProperty("play").GetProperty("meta").GetProperty("mim_type").GetString()); + Assert.Equal("LISTEN", jcpConfig.GetProperty("listen").GetProperty("type").GetString()); + Assert.Equal("shared/yes_no", jcpConfig.GetProperty("listen").GetProperty("contexts")[0].GetString()); + } var session = _store.FindSessionByToken(token); Assert.NotNull(session);