From bfff2d310dd532fc53f463c1f8b1a250ff04e3e0 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Tue, 26 May 2026 23:31:20 +0200 Subject: [PATCH] Implement Phase 6R-AI custom-endpoint runtime routing --- .../HyperTwistHttpSpeechClient.cpp | 178 ++++++++-- .../HyperTwistSpeechClient.h | 3 + ...e6RAICustomEndpointRuntimeContractTest.cpp | 334 ++++++++++++++++++ ...hase6RXProviderProfileByokContractTest.cpp | 8 +- ...se6RYProviderRoutingPolicyContractTest.cpp | 12 +- ...OUTING_IMPLEMENTATION_PACKET_2026-05-26.md | 158 +++++++++ ...E_ROUTING_PREPARATION_PACKET_2026-05-26.md | 137 +++++++ ...PERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md | 10 +- ...NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md | 10 +- .../HyperTwist/FEATURE_REGISTRY.md | 3 +- .../HyperTwist/ROADMAP.md | 47 ++- 11 files changed, 833 insertions(+), 67 deletions(-) create mode 100644 UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RAICustomEndpointRuntimeContractTest.cpp create mode 100644 docs/arch/HYPERTWIST_PHASE6R_AI_FIRST_PARTY_PROVIDER_NEUTRAL_CUSTOM_ENDPOINT_RUNTIME_ROUTING_IMPLEMENTATION_PACKET_2026-05-26.md create mode 100644 docs/arch/HYPERTWIST_PHASE6R_AI_FIRST_PARTY_PROVIDER_NEUTRAL_CUSTOM_ENDPOINT_RUNTIME_ROUTING_PREPARATION_PACKET_2026-05-26.md diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp index 8ffc685..717de3e 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp @@ -52,6 +52,51 @@ namespace HyperTwistHttpSpeechClientInternal return FString::Printf(TEXT("%s/%s"), *TrimmedBase, *TrimmedPath); } + const FHyperTwistSpeechProviderProfile* ResolveProviderProfile( + const FHyperTwistSpeechSessionConfig* SessionConfig + ) + { + if (SessionConfig != nullptr + && SessionConfig->ProviderProfileDefinition.IsStructurallyValid()) + { + return &SessionConfig->ProviderProfileDefinition; + } + + return nullptr; + } + + FString ResolveServiceBaseUrl( + const UHyperTwistHttpSpeechClient& Client, + const FHyperTwistSpeechSessionConfig* SessionConfig + ) + { + if (const FHyperTwistSpeechProviderProfile* Profile = ResolveProviderProfile(SessionConfig)) + { + if (!Profile->EndpointBaseUrl.IsEmpty()) + { + return Profile->EndpointBaseUrl; + } + } + + return Client.ServiceBaseUrl; + } + + FString ResolveProviderLabel( + const UHyperTwistHttpSpeechClient& Client, + const FHyperTwistSpeechSessionConfig* SessionConfig + ) + { + if (const FHyperTwistSpeechProviderProfile* Profile = ResolveProviderProfile(SessionConfig)) + { + if (!Profile->DisplayLabel.IsEmpty()) + { + return Profile->DisplayLabel; + } + } + + return Client.ProviderLabel; + } + struct FHttpJsonResponse { bool bCompleted = false; @@ -68,6 +113,8 @@ namespace HyperTwistHttpSpeechClientInternal const FString& RequestJson, FHttpJsonResponse& OutResponse) { + const TSharedRef ResponseState = + MakeShared(); OutResponse = FHttpJsonResponse(); if (Url.IsEmpty()) @@ -93,35 +140,35 @@ namespace HyperTwistHttpSpeechClientInternal } Request->OnProcessRequestComplete().BindLambda( - [&OutResponse](FHttpRequestPtr, FHttpResponsePtr Response, bool bWasSuccessful) + [ResponseState](FHttpRequestPtr, FHttpResponsePtr Response, bool bWasSuccessful) { - OutResponse.bCompleted = true; + ResponseState->bCompleted = true; if (Response.IsValid()) { - OutResponse.StatusCode = Response->GetResponseCode(); - OutResponse.ResponseBody = Response->GetContentAsString(); + ResponseState->StatusCode = Response->GetResponseCode(); + ResponseState->ResponseBody = Response->GetContentAsString(); } if (!bWasSuccessful) { - OutResponse.Error = TEXT("request-failed"); + ResponseState->Error = TEXT("request-failed"); return; } if (!Response.IsValid()) { - OutResponse.Error = TEXT("response-missing"); + ResponseState->Error = TEXT("response-missing"); return; } - if (!EHttpResponseCodes::IsOk(OutResponse.StatusCode)) + if (!EHttpResponseCodes::IsOk(ResponseState->StatusCode)) { - OutResponse.Error = FString::Printf(TEXT("http-%d"), OutResponse.StatusCode); + ResponseState->Error = FString::Printf(TEXT("http-%d"), ResponseState->StatusCode); return; } - OutResponse.bSucceeded = true; + ResponseState->bSucceeded = true; }); if (!Request->ProcessRequest()) @@ -132,19 +179,22 @@ namespace HyperTwistHttpSpeechClientInternal const double TimeoutSeconds = FMath::Max(static_cast(Client.RequestTimeoutSeconds), 0.1); const double Deadline = FPlatformTime::Seconds() + TimeoutSeconds; - while (!OutResponse.bCompleted && FPlatformTime::Seconds() < Deadline) + while (!ResponseState->bCompleted && FPlatformTime::Seconds() < Deadline) { FHttpModule::Get().GetHttpManager().Tick(0.01f); FPlatformProcess::Sleep(0.01f); } - if (!OutResponse.bCompleted) + if (!ResponseState->bCompleted) { + Request->OnProcessRequestComplete().Unbind(); Request->CancelRequest(); - OutResponse.Error = TEXT("request-timeout"); + ResponseState->Error = TEXT("request-timeout"); + OutResponse = *ResponseState; return false; } + OutResponse = *ResponseState; return OutResponse.bSucceeded; } } @@ -152,6 +202,9 @@ namespace HyperTwistHttpSpeechClientInternal bool UHyperTwistHttpSpeechClient::OpenSpeechSession(const FHyperTwistSpeechSessionConfig& Config, FString& OutError) { OutError.Reset(); + LastRequestedSessionConfig = Config; + const FString EffectiveServiceBaseUrl = + HyperTwistHttpSpeechClientInternal::ResolveServiceBaseUrl(*this, &Config); FString RequestJson; if (!HyperTwistHttpSpeechClientInternal::SerializeStruct(Config, RequestJson)) @@ -165,7 +218,7 @@ bool UHyperTwistHttpSpeechClient::OpenSpeechSession(const FHyperTwistSpeechSessi if (!HyperTwistHttpSpeechClientInternal::ExecuteJsonRequest( *this, TEXT("POST"), - HyperTwistHttpSpeechClientInternal::BuildUrl(ServiceBaseUrl, OpenSessionPath), + HyperTwistHttpSpeechClientInternal::BuildUrl(EffectiveServiceBaseUrl, OpenSessionPath), RequestJson, Response)) { @@ -187,6 +240,8 @@ FHyperTwistSpeechTranscriptResult UHyperTwistHttpSpeechClient::TranscribeSpeechU Result.SessionId = Utterance.SessionId; Result.UtteranceId = Utterance.UtteranceId; const FHyperTwistSpeechSessionConfig* SessionConfig = SessionConfigs.Find(Utterance.SessionId); + const FString EffectiveServiceBaseUrl = + HyperTwistHttpSpeechClientInternal::ResolveServiceBaseUrl(*this, SessionConfig); FString RequestJson; if (!HyperTwistHttpSpeechClientInternal::SerializeStruct(Utterance, RequestJson)) @@ -200,7 +255,7 @@ FHyperTwistSpeechTranscriptResult UHyperTwistHttpSpeechClient::TranscribeSpeechU if (!HyperTwistHttpSpeechClientInternal::ExecuteJsonRequest( *this, TEXT("POST"), - HyperTwistHttpSpeechClientInternal::BuildUrl(ServiceBaseUrl, TranscribeUtterancePath), + HyperTwistHttpSpeechClientInternal::BuildUrl(EffectiveServiceBaseUrl, TranscribeUtterancePath), RequestJson, Response)) { @@ -286,12 +341,15 @@ FHyperTwistSpeechTranscriptResult UHyperTwistHttpSpeechClient::TranscribeSpeechU bool UHyperTwistHttpSpeechClient::CloseSpeechSession(const FString& SessionId, FString& OutError) { OutError.Reset(); + const FHyperTwistSpeechSessionConfig* SessionConfig = SessionConfigs.Find(SessionId); + const FString EffectiveServiceBaseUrl = + HyperTwistHttpSpeechClientInternal::ResolveServiceBaseUrl(*this, SessionConfig); HyperTwistHttpSpeechClientInternal::FHttpJsonResponse Response; if (!HyperTwistHttpSpeechClientInternal::ExecuteJsonRequest( *this, TEXT("POST"), - HyperTwistHttpSpeechClientInternal::BuildUrl(ServiceBaseUrl, CloseSessionPath), + HyperTwistHttpSpeechClientInternal::BuildUrl(EffectiveServiceBaseUrl, CloseSessionPath), HyperTwistHttpSpeechClientInternal::MakeSessionIdJson(SessionId), Response)) { @@ -308,11 +366,28 @@ bool UHyperTwistHttpSpeechClient::CloseSpeechSession(const FString& SessionId, F FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHealth() const { FHyperTwistSpeechServiceHealth Health = UHyperTwistContractLibrary::MakeMockSpeechServiceHealth(); + const FHyperTwistSpeechSessionConfig* HealthSessionConfig = nullptr; + for (auto It = SessionConfigs.CreateConstIterator(); It; ++It) + { + HealthSessionConfig = &It.Value(); + break; + } + if (HealthSessionConfig == nullptr && LastRequestedSessionConfig.IsStructurallyValid()) + { + HealthSessionConfig = &LastRequestedSessionConfig; + } + const FHyperTwistSpeechSessionConfig ProviderDefaults = - UHyperTwistContractLibrary::MakeSampleSpeechSessionConfig(); - Health.ProviderLabel = ProviderLabel; + HealthSessionConfig != nullptr && HealthSessionConfig->IsStructurallyValid() + ? *HealthSessionConfig + : UHyperTwistContractLibrary::MakeSampleSpeechSessionConfig(); + const FString EffectiveProviderLabel = + HyperTwistHttpSpeechClientInternal::ResolveProviderLabel(*this, HealthSessionConfig); + const FString EffectiveServiceBaseUrl = + HyperTwistHttpSpeechClientInternal::ResolveServiceBaseUrl(*this, HealthSessionConfig); + Health.ProviderLabel = EffectiveProviderLabel; Health.ServiceVersion = TEXT("provider-unavailable/v1"); - Health.ServiceEndpoint = ServiceBaseUrl; + Health.ServiceEndpoint = EffectiveServiceBaseUrl; Health.bReady = false; Health.Capabilities = { TEXT("transport:http"), @@ -377,26 +452,26 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal if (!HyperTwistHttpSpeechClientInternal::ExecuteJsonRequest( *this, TEXT("GET"), - HyperTwistHttpSpeechClientInternal::BuildUrl(ServiceBaseUrl, HealthPath), + HyperTwistHttpSpeechClientInternal::BuildUrl(EffectiveServiceBaseUrl, HealthPath), FString(), Response)) { LastTransportError = Response.Error; Health.ProviderProfileId = ProviderDefaults.ProviderProfileId; Health.ProviderProfileDefinition = ProviderDefaults.ProviderProfileDefinition; - Health.ProviderProfileDefinition.DisplayLabel = ProviderLabel.IsEmpty() + Health.ProviderProfileDefinition.DisplayLabel = EffectiveProviderLabel.IsEmpty() ? Health.ProviderProfileDefinition.DisplayLabel - : ProviderLabel; - Health.ProviderProfileDefinition.EndpointBaseUrl = ServiceBaseUrl; + : EffectiveProviderLabel; + Health.ProviderProfileDefinition.EndpointBaseUrl = EffectiveServiceBaseUrl; Health.ByokCustodyProfileId = ProviderDefaults.ByokCustodyProfileId; Health.ByokCustodyProfileDefinition = ProviderDefaults.ByokCustodyProfileDefinition; Health.ProviderRoutingPolicyId = ProviderDefaults.ProviderRoutingPolicyId; Health.ProviderRoutingPolicyDefinition = ProviderDefaults.ProviderRoutingPolicyDefinition; Health.ProviderRouteDecision = ProviderDefaults.ProviderRouteDecision; - Health.ProviderRouteDecision.RouteStateId = ServiceBaseUrl.IsEmpty() + Health.ProviderRouteDecision.RouteStateId = EffectiveServiceBaseUrl.IsEmpty() ? TEXT("route-awaiting-endpoint") : TEXT("route-health-degraded"); - Health.ProviderRouteDecision.DecisionReason = ServiceBaseUrl.IsEmpty() + Health.ProviderRouteDecision.DecisionReason = EffectiveServiceBaseUrl.IsEmpty() ? TEXT("provider-backed-sidecar-route-unavailable") : TEXT("provider-backed-sidecar-health-degraded"); Health.ProviderRouteDecision.FailureReason = LastTransportError; @@ -410,9 +485,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal { LastTransportError = TEXT("invalid-provider-response"); Health = UHyperTwistContractLibrary::MakeMockSpeechServiceHealth(); - Health.ProviderLabel = ProviderLabel; + Health.ProviderLabel = EffectiveProviderLabel; Health.ServiceVersion = TEXT("provider-unavailable/v1"); - Health.ServiceEndpoint = ServiceBaseUrl; + Health.ServiceEndpoint = EffectiveServiceBaseUrl; Health.bReady = false; Health.Capabilities = { TEXT("transport:http"), @@ -474,10 +549,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal }; Health.ProviderProfileId = ProviderDefaults.ProviderProfileId; Health.ProviderProfileDefinition = ProviderDefaults.ProviderProfileDefinition; - Health.ProviderProfileDefinition.DisplayLabel = ProviderLabel.IsEmpty() + Health.ProviderProfileDefinition.DisplayLabel = EffectiveProviderLabel.IsEmpty() ? Health.ProviderProfileDefinition.DisplayLabel - : ProviderLabel; - Health.ProviderProfileDefinition.EndpointBaseUrl = ServiceBaseUrl; + : EffectiveProviderLabel; + Health.ProviderProfileDefinition.EndpointBaseUrl = EffectiveServiceBaseUrl; Health.ByokCustodyProfileId = ProviderDefaults.ByokCustodyProfileId; Health.ByokCustodyProfileDefinition = ProviderDefaults.ByokCustodyProfileDefinition; Health.ProviderRoutingPolicyId = ProviderDefaults.ProviderRoutingPolicyId; @@ -494,7 +569,7 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal if (Health.ProviderLabel.IsEmpty()) { - Health.ProviderLabel = ProviderLabel; + Health.ProviderLabel = EffectiveProviderLabel; } Health.Capabilities.AddUnique(TEXT("microphoneCaptureShell")); Health.Capabilities.AddUnique(TEXT("devicePermissionShell")); @@ -516,7 +591,7 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal Health.SupportedOrchestrationProfiles.AddUnique(TEXT("python-batch-transcribe-v1")); if (Health.ServiceEndpoint.IsEmpty()) { - Health.ServiceEndpoint = ServiceBaseUrl; + Health.ServiceEndpoint = EffectiveServiceBaseUrl; } Health.Capabilities.AddUnique(TEXT("transport:http")); @@ -561,10 +636,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal { Health.ProviderProfileDefinition = ProviderDefaults.ProviderProfileDefinition; } - Health.ProviderProfileDefinition.DisplayLabel = ProviderLabel.IsEmpty() + Health.ProviderProfileDefinition.DisplayLabel = EffectiveProviderLabel.IsEmpty() ? Health.ProviderProfileDefinition.DisplayLabel - : ProviderLabel; - Health.ProviderProfileDefinition.EndpointBaseUrl = ServiceBaseUrl; + : EffectiveProviderLabel; + Health.ProviderProfileDefinition.EndpointBaseUrl = EffectiveServiceBaseUrl; if (Health.ByokCustodyProfileId.IsEmpty()) { Health.ByokCustodyProfileId = ProviderDefaults.ByokCustodyProfileId; @@ -585,6 +660,41 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal { Health.ProviderRouteDecision = ProviderDefaults.ProviderRouteDecision; } + if (Health.ProviderRouteDecision.SelectedProviderProfileId.IsEmpty()) + { + Health.ProviderRouteDecision.SelectedProviderProfileId = Health.ProviderProfileId; + } + if (Health.ProviderRouteDecision.RequestedProviderProfileId.IsEmpty()) + { + Health.ProviderRouteDecision.RequestedProviderProfileId = Health.ProviderProfileId; + } + if (Health.ProviderRouteDecision.SelectedProviderClass.IsEmpty()) + { + Health.ProviderRouteDecision.SelectedProviderClass = + Health.ProviderProfileDefinition.ProviderClass; + } + if (Health.ProviderRouteDecision.SelectedEndpointClass.IsEmpty()) + { + Health.ProviderRouteDecision.SelectedEndpointClass = + Health.ProviderProfileDefinition.EndpointClass; + } + if (Health.ProviderRouteDecision.SelectedTransportKind.IsEmpty()) + { + Health.ProviderRouteDecision.SelectedTransportKind = TEXT("http"); + } + if (Health.ProviderRouteDecision.SelectedServiceLaneId.IsEmpty()) + { + Health.ProviderRouteDecision.SelectedServiceLaneId = + ProviderDefaults.ProviderRouteDecision.SelectedServiceLaneId; + } + Health.ProviderRouteDecision.bSelectedCustomEndpointRoute = + Health.ProviderProfileDefinition.bUserLabeled + && Health.ProviderProfileDefinition.bSupportsCustomBaseUrl + && !Health.ProviderProfileDefinition.EndpointBaseUrl.IsEmpty() + && !Health.ProviderProfileDefinition.EndpointBaseUrl.Contains( + TEXT("127.0.0.1"), + ESearchCase::IgnoreCase + ); if (Health.PayloadCustodyProfileId.IsEmpty()) { Health.PayloadCustodyProfileId = TEXT("downloadable-model-payload-custody-v1"); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistSpeechClient.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistSpeechClient.h index 40b4501..b7a6b00 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistSpeechClient.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistSpeechClient.h @@ -95,5 +95,8 @@ private: UPROPERTY() TMap SessionConfigs; + UPROPERTY() + FHyperTwistSpeechSessionConfig LastRequestedSessionConfig; + mutable FString LastTransportError; }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RAICustomEndpointRuntimeContractTest.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RAICustomEndpointRuntimeContractTest.cpp new file mode 100644 index 0000000..e531969 --- /dev/null +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RAICustomEndpointRuntimeContractTest.cpp @@ -0,0 +1,334 @@ +// Copyright HyperTwist, Inc. All Rights Reserved. + +#include "Misc/AutomationTest.h" + +#include "Engine/GameInstance.h" +#include "HyperTwistBootstrap/HyperTwistContractLibrary.h" +#include "HyperTwistRecognition/HyperTwistSpeechClient.h" +#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h" +#include "UObject/UnrealType.h" + +#if WITH_AUTOMATION_TESTS + +namespace HyperTwistWhisperCppPhase6RAITestInternal +{ + const FString& GetCustomEndpointUrl() + { + static const FString EndpointUrl = TEXT("http://localhost:9/openai-compatible"); + return EndpointUrl; + } + + const FString& GetCustomProviderProfileId() + { + static const FString ProviderProfileId = + TEXT("speech-provider/custom-openai-endpoint-profile-v1"); + return ProviderProfileId; + } + + const FString& GetCustomProviderLabel() + { + static const FString ProviderLabel = TEXT("Operator Custom Endpoint"); + return ProviderLabel; + } + + FHyperTwistTrainingDeck MakeSpeechDeck() + { + FHyperTwistTrainingDeck Deck; + Deck.DeckId = TEXT("phase6r-ai/custom-endpoint-runtime"); + Deck.Title = TEXT("Phase 6R-AI Custom Endpoint Runtime"); + Deck.DeliveryModes = { + EHyperTwistTrainingDeliveryMode::CoachReviewed + }; + + FHyperTwistTrainingCase TrainingCase; + TrainingCase.CaseId = TEXT("phase6r-ai-case"); + TrainingCase.PuzzleId = TEXT("cube/3x3x3"); + TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Sequence; + TrainingCase.PromptLabel = TEXT("Phase 6R-AI Coach Speech"); + TrainingCase.AllowedDeliveryModes = { + EHyperTwistTrainingDeliveryMode::CoachReviewed + }; + Deck.Cases = {TrainingCase}; + return Deck; + } + + void ForceSpeechClientKind(UHyperTwistTrainingSubsystem* TrainingSubsystem, const FString& ClientKind) + { + if (TrainingSubsystem == nullptr) + { + return; + } + + if (FStrProperty* SpeechClientKindProperty = FindFProperty( + UHyperTwistTrainingSubsystem::StaticClass(), + TEXT("CompanionSpeechClientKind") + )) + { + SpeechClientKindProperty->SetPropertyValue_InContainer(TrainingSubsystem, ClientKind); + } + } + + UHyperTwistTrainingSubsystem* MakeSpeechSubsystem(const FString& SessionId) + { + UGameInstance* GameInstance = NewObject(GetTransientPackage()); + if (GameInstance == nullptr) + { + return nullptr; + } + + UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject(GameInstance); + if (TrainingSubsystem == nullptr) + { + return nullptr; + } + + ForceSpeechClientKind(TrainingSubsystem, TEXT("http-sidecar")); + const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck( + MakeSpeechDeck(), + TEXT("phase6r-ai-user"), + SessionId, + EHyperTwistTrainingDeliveryMode::CoachReviewed + ); + return RunState.IsStructurallyValid() ? TrainingSubsystem : nullptr; + } + + FHyperTwistTrainingCompanionSpeechSessionState* ResolveActiveSpeechSessionState( + UHyperTwistTrainingSubsystem* TrainingSubsystem + ) + { + if (TrainingSubsystem == nullptr) + { + return nullptr; + } + + if (FStructProperty* SessionStateProperty = FindFProperty( + UHyperTwistTrainingSubsystem::StaticClass(), + TEXT("ActiveCompanionSpeechSessionState") + )) + { + return SessionStateProperty->ContainerPtrToValuePtr( + TrainingSubsystem + ); + } + + return nullptr; + } + + UHyperTwistHttpSpeechClient* ResolveHttpSpeechClient(UHyperTwistTrainingSubsystem* TrainingSubsystem) + { + if (TrainingSubsystem == nullptr) + { + return nullptr; + } + + FString CloseError; + TrainingSubsystem->CloseActiveCompanionSpeechSession(CloseError); + + if (FObjectPropertyBase* ClientProperty = FindFProperty( + UHyperTwistTrainingSubsystem::StaticClass(), + TEXT("ActiveCompanionSpeechClientObject") + )) + { + return Cast( + ClientProperty->GetObjectPropertyValue_InContainer(TrainingSubsystem) + ); + } + + return nullptr; + } + + void ForceCustomEndpointSessionConfig( + UHyperTwistTrainingSubsystem* TrainingSubsystem, + const FString& SessionId + ) + { + if (FHyperTwistTrainingCompanionSpeechSessionState* SessionState = + ResolveActiveSpeechSessionState(TrainingSubsystem)) + { + SessionState->SessionConfig = UHyperTwistContractLibrary::MakeSampleSpeechSessionConfig(); + SessionState->SessionConfig.SessionId = SessionId; + SessionState->SessionConfig.ProviderProfileId = GetCustomProviderProfileId(); + + FHyperTwistSpeechProviderProfile& ProviderProfile = + SessionState->SessionConfig.ProviderProfileDefinition; + ProviderProfile.ProviderProfileId = GetCustomProviderProfileId(); + ProviderProfile.DisplayLabel = GetCustomProviderLabel(); + ProviderProfile.ProviderClass = TEXT("openai-compatible-custom-endpoint"); + ProviderProfile.EndpointClass = TEXT("openai-compatible-http-api"); + ProviderProfile.EndpointReferenceId = TEXT("speech/provider-profile/custom-openai-endpoint"); + ProviderProfile.EndpointBaseUrl = GetCustomEndpointUrl(); + ProviderProfile.AuthMaterialSource = TEXT("first-party-out-of-band-secret-reference"); + ProviderProfile.AuthReferenceId = TEXT("speech/byok/custom-openai-endpoint/default"); + ProviderProfile.ModelSelectionMode = TEXT("per-profile-model-map"); + ProviderProfile.bEnabled = true; + ProviderProfile.bUserLabeled = true; + ProviderProfile.bSupportsEnableDisable = true; + ProviderProfile.bSupportsCustomBaseUrl = true; + ProviderProfile.bPreservesFirstPartyCustody = true; + + SessionState->LastError.Reset(); + SessionState->ServiceHealth = FHyperTwistSpeechServiceHealth(); + } + } + + void PrepareCustomEndpointRuntime( + UHyperTwistTrainingSubsystem* TrainingSubsystem, + UHyperTwistHttpSpeechClient*& OutHttpSpeechClient + ) + { + OutHttpSpeechClient = ResolveHttpSpeechClient(TrainingSubsystem); + if (OutHttpSpeechClient != nullptr) + { + OutHttpSpeechClient->ServiceBaseUrl.Reset(); + OutHttpSpeechClient->RequestTimeoutSeconds = 0.25f; + } + + ForceCustomEndpointSessionConfig( + TrainingSubsystem, + TEXT("phase6r-ai-custom-endpoint-session") + ); + } +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RAICustomEndpointSessionConfigTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.AI.CustomEndpointSessionConfig", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RAICustomEndpointSessionConfigTest::RunTest( + const FString& Parameters +) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RAITestInternal::MakeSpeechSubsystem( + TEXT("phase6r-ai-config-session") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AI."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + UHyperTwistHttpSpeechClient* HttpSpeechClient = nullptr; + HyperTwistWhisperCppPhase6RAITestInternal::PrepareCustomEndpointRuntime( + TrainingSubsystem, + HttpSpeechClient + ); + TestNotNull(TEXT("The provider-backed speech client must be available for Phase 6R-AI."), HttpSpeechClient); + if (HttpSpeechClient == nullptr) + { + return false; + } + + FString OpenError; + TestFalse(TEXT("The custom endpoint route should fail cleanly without a live endpoint."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestNotEqual(TEXT("The custom endpoint route must not collapse back to a missing-base-URL failure."), OpenError, FString(TEXT("service-base-url-missing"))); + + const FHyperTwistTrainingCompanionSpeechSessionState SessionState = + TrainingSubsystem->GetActiveCompanionSpeechSessionState(); + const FHyperTwistSpeechSessionConfig& Config = SessionState.SessionConfig; + + TestTrue(TEXT("The custom endpoint session config must remain structurally valid."), Config.IsStructurallyValid()); + TestEqual(TEXT("The custom endpoint session config must keep the provider profile id."), Config.ProviderProfileId, HyperTwistWhisperCppPhase6RAITestInternal::GetCustomProviderProfileId()); + TestEqual(TEXT("The custom endpoint session config must keep the provider class."), Config.ProviderProfileDefinition.ProviderClass, TEXT("openai-compatible-custom-endpoint")); + TestEqual(TEXT("The custom endpoint session config must keep the endpoint class."), Config.ProviderProfileDefinition.EndpointClass, TEXT("openai-compatible-http-api")); + TestEqual(TEXT("The custom endpoint session config must keep the endpoint base URL."), Config.ProviderProfileDefinition.EndpointBaseUrl, HyperTwistWhisperCppPhase6RAITestInternal::GetCustomEndpointUrl()); + TestTrue(TEXT("The provider route decision must mark the custom endpoint route as selected."), Config.ProviderRouteDecision.bSelectedCustomEndpointRoute); + TestEqual(TEXT("The preflight route decision must stay ready when a custom endpoint is configured."), Config.ProviderRouteDecision.RouteStateId, TEXT("route-ready")); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RAICustomEndpointHealthFallbackTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.AI.CustomEndpointHealthFallback", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RAICustomEndpointHealthFallbackTest::RunTest( + const FString& Parameters +) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RAITestInternal::MakeSpeechSubsystem( + TEXT("phase6r-ai-health-session") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AI."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + UHyperTwistHttpSpeechClient* HttpSpeechClient = nullptr; + HyperTwistWhisperCppPhase6RAITestInternal::PrepareCustomEndpointRuntime( + TrainingSubsystem, + HttpSpeechClient + ); + TestNotNull(TEXT("The provider-backed speech client must be available for the custom endpoint health test."), HttpSpeechClient); + if (HttpSpeechClient == nullptr) + { + return false; + } + + FString OpenError; + TestFalse(TEXT("The custom endpoint route should fail cleanly without a live endpoint."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + + const FHyperTwistTrainingCompanionSpeechSessionState SessionState = + TrainingSubsystem->GetActiveCompanionSpeechSessionState(); + const FHyperTwistSpeechServiceHealth& Health = SessionState.ServiceHealth; + + TestTrue(TEXT("The fallback service health must remain structurally valid."), Health.IsStructurallyValid()); + TestEqual(TEXT("The fallback service health must keep the custom provider label."), Health.ProviderLabel, HyperTwistWhisperCppPhase6RAITestInternal::GetCustomProviderLabel()); + TestEqual(TEXT("The fallback service health must keep the custom endpoint service URL."), Health.ServiceEndpoint, HyperTwistWhisperCppPhase6RAITestInternal::GetCustomEndpointUrl()); + TestEqual(TEXT("The fallback provider profile must keep the custom provider profile id."), Health.ProviderProfileId, HyperTwistWhisperCppPhase6RAITestInternal::GetCustomProviderProfileId()); + TestEqual(TEXT("The fallback provider profile must keep the custom endpoint base URL."), Health.ProviderProfileDefinition.EndpointBaseUrl, HyperTwistWhisperCppPhase6RAITestInternal::GetCustomEndpointUrl()); + TestEqual(TEXT("The fallback route state must degrade cleanly instead of waiting for an endpoint."), Health.ProviderRouteDecision.RouteStateId, TEXT("route-health-degraded")); + TestTrue(TEXT("The fallback route state must preserve the custom endpoint selection flag."), Health.ProviderRouteDecision.bSelectedCustomEndpointRoute); + TestEqual(TEXT("The fallback route state must preserve the open-session failure."), Health.LastError, OpenError); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RAIDirectClientHealthReflectionTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.AI.DirectClientHealthReflection", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RAIDirectClientHealthReflectionTest::RunTest( + const FString& Parameters +) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RAITestInternal::MakeSpeechSubsystem( + TEXT("phase6r-ai-direct-health-session") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AI."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + UHyperTwistHttpSpeechClient* HttpSpeechClient = nullptr; + HyperTwistWhisperCppPhase6RAITestInternal::PrepareCustomEndpointRuntime( + TrainingSubsystem, + HttpSpeechClient + ); + TestNotNull(TEXT("The provider-backed speech client must be available for the direct health test."), HttpSpeechClient); + if (HttpSpeechClient == nullptr) + { + return false; + } + + FString OpenError; + TestFalse(TEXT("The custom endpoint route should fail cleanly without a live endpoint."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + + const FHyperTwistSpeechServiceHealth DirectHealth = HttpSpeechClient->GetSpeechServiceHealth(); + TestTrue(TEXT("The direct client health must remain structurally valid."), DirectHealth.IsStructurallyValid()); + TestEqual(TEXT("The direct client health must keep the custom provider label."), DirectHealth.ProviderLabel, HyperTwistWhisperCppPhase6RAITestInternal::GetCustomProviderLabel()); + TestEqual(TEXT("The direct client health must keep the custom endpoint service URL."), DirectHealth.ServiceEndpoint, HyperTwistWhisperCppPhase6RAITestInternal::GetCustomEndpointUrl()); + TestEqual(TEXT("The direct client health must keep the custom endpoint base URL."), DirectHealth.ProviderProfileDefinition.EndpointBaseUrl, HyperTwistWhisperCppPhase6RAITestInternal::GetCustomEndpointUrl()); + TestTrue(TEXT("The direct client health must preserve the custom endpoint selection flag."), DirectHealth.ProviderRouteDecision.bSelectedCustomEndpointRoute); + return true; +} + +#endif diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RXProviderProfileByokContractTest.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RXProviderProfileByokContractTest.cpp index f7fc4ee..83095a2 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RXProviderProfileByokContractTest.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RXProviderProfileByokContractTest.cpp @@ -209,8 +209,8 @@ bool FHyperTwistWhisperCppPhase6RXProviderBackedByokFallbackTest::RunTest(const HttpSpeechClient->ServiceBaseUrl.Reset(); FString OpenError; - TestFalse(TEXT("The provider-backed provider-profile route must fail cleanly when no provider endpoint is configured."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); - TestEqual(TEXT("The provider-backed provider-profile route must report the missing service base URL."), OpenError, TEXT("service-base-url-missing")); + TestFalse(TEXT("The provider-backed provider-profile route must fail cleanly when the configured provider endpoint is unavailable."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestEqual(TEXT("The provider-backed provider-profile route must report a provider-backed transport failure once the provider profile endpoint is selected."), OpenError, TEXT("request-failed")); const FHyperTwistTrainingCompanionSpeechSessionState SessionState = TrainingSubsystem->GetActiveCompanionSpeechSessionState(); @@ -221,13 +221,13 @@ bool FHyperTwistWhisperCppPhase6RXProviderBackedByokFallbackTest::RunTest(const TestEqual(TEXT("The provider-backed fallback must keep the local self-hosted endpoint class."), Health.ProviderProfileDefinition.EndpointClass, TEXT("local-self-hosted-http")); TestEqual(TEXT("The provider-backed fallback must keep the first-party auth custody posture."), Health.ProviderProfileDefinition.AuthMaterialSource, TEXT("first-party-out-of-band-secret-reference")); TestTrue(TEXT("The provider-backed fallback must preserve custom base URL support."), Health.ProviderProfileDefinition.bSupportsCustomBaseUrl); - TestFalse(TEXT("The provider-backed fallback must keep the endpoint base URL empty when the configured route is empty."), !Health.ProviderProfileDefinition.EndpointBaseUrl.IsEmpty()); + TestEqual(TEXT("The provider-backed fallback must preserve the provider-profile endpoint base URL when the runtime selects that route."), Health.ProviderProfileDefinition.EndpointBaseUrl, TEXT("http://127.0.0.1:8766")); TestFalse(TEXT("The provider-backed fallback must not drop the provider endpoint reference id."), Health.ProviderProfileDefinition.EndpointReferenceId.IsEmpty()); TestEqual(TEXT("The provider-backed fallback must keep the BYOK custody profile id."), Health.ByokCustodyProfileId, TEXT("speech-byok-custody-profile-v1")); TestTrue(TEXT("The provider-backed fallback must preserve provider-profile custody capability."), Health.Capabilities.Contains(TEXT("providerProfileCustody"))); TestTrue(TEXT("The provider-backed fallback must preserve BYOK custody capability."), Health.Capabilities.Contains(TEXT("byokCustody"))); TestTrue(TEXT("The provider-backed fallback must preserve OpenAI-compatible custom endpoint support."), Health.ByokCustodyProfileDefinition.bSupportsOpenAiCompatibleCustomEndpoints); - TestEqual(TEXT("The provider-backed fallback must keep the route error in the shell state."), SessionState.MicrophoneShellState.LastRouteError, TEXT("service-base-url-missing")); + TestEqual(TEXT("The provider-backed fallback must keep the provider-backed transport failure in the shell state."), SessionState.MicrophoneShellState.LastRouteError, TEXT("request-failed")); return true; } diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RYProviderRoutingPolicyContractTest.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RYProviderRoutingPolicyContractTest.cpp index 867ed84..d47adaf 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RYProviderRoutingPolicyContractTest.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RYProviderRoutingPolicyContractTest.cpp @@ -218,8 +218,8 @@ bool FHyperTwistWhisperCppPhase6RYProviderBackedRoutingFallbackTest::RunTest(con HttpSpeechClient->ServiceBaseUrl.Reset(); FString OpenError; - TestFalse(TEXT("The provider-backed routing path must fail cleanly when no provider endpoint is configured."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); - TestEqual(TEXT("The provider-backed routing path must report the missing service base URL."), OpenError, TEXT("service-base-url-missing")); + TestFalse(TEXT("The provider-backed routing path must fail cleanly when the configured provider endpoint is unavailable."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestEqual(TEXT("The provider-backed routing path must report a provider-backed transport failure once the provider profile endpoint is selected."), OpenError, TEXT("request-failed")); const FHyperTwistTrainingCompanionSpeechSessionState SessionState = TrainingSubsystem->GetActiveCompanionSpeechSessionState(); @@ -237,14 +237,14 @@ bool FHyperTwistWhisperCppPhase6RYProviderBackedRoutingFallbackTest::RunTest(con TestEqual(TEXT("The provider-backed route decision must target the local HTTP provider profile."), Health.ProviderRouteDecision.SelectedProviderProfileId, TEXT("speech-provider/local-http-sidecar-profile-v1")); TestEqual(TEXT("The provider-backed route decision must preserve the local self-hosted endpoint class."), Health.ProviderRouteDecision.SelectedEndpointClass, TEXT("local-self-hosted-http")); TestEqual(TEXT("The provider-backed route decision must preserve HTTP transport."), Health.ProviderRouteDecision.SelectedTransportKind, TEXT("http")); - TestEqual(TEXT("The provider-backed route decision must report the awaiting-endpoint state."), Health.ProviderRouteDecision.RouteStateId, TEXT("route-awaiting-endpoint")); - TestEqual(TEXT("The provider-backed route decision must expose the unavailability reason."), Health.ProviderRouteDecision.DecisionReason, TEXT("provider-backed-sidecar-route-unavailable")); - TestEqual(TEXT("The provider-backed route decision must expose the missing-endpoint failure."), Health.ProviderRouteDecision.FailureReason, TEXT("service-base-url-missing")); + TestEqual(TEXT("The provider-backed route decision must report the health-degraded state when the configured provider endpoint is unavailable."), Health.ProviderRouteDecision.RouteStateId, TEXT("route-health-degraded")); + TestEqual(TEXT("The provider-backed route decision must expose the provider-backed health-degraded reason."), Health.ProviderRouteDecision.DecisionReason, TEXT("provider-backed-sidecar-health-degraded")); + TestEqual(TEXT("The provider-backed route decision must expose the provider-backed transport failure."), Health.ProviderRouteDecision.FailureReason, TEXT("request-failed")); TestTrue(TEXT("The provider-backed route decision must mark a provider-backed route as requested."), Health.ProviderRouteDecision.bProviderBackedRouteRequested); TestTrue(TEXT("The provider-backed route decision must mark a provider-backed route as selected."), Health.ProviderRouteDecision.bProviderBackedRouteSelected); TestTrue(TEXT("The provider-backed route decision must preserve fallback health posture."), Health.ProviderRouteDecision.bUsedFallbackHealthPosture); TestFalse(TEXT("The provider-backed route decision must not report a ready route."), Health.ProviderRouteDecision.bRouteReady); - TestEqual(TEXT("The shell state must preserve the route error."), SessionState.MicrophoneShellState.LastRouteError, TEXT("service-base-url-missing")); + TestEqual(TEXT("The shell state must preserve the provider-backed transport failure."), SessionState.MicrophoneShellState.LastRouteError, TEXT("request-failed")); return true; } diff --git a/docs/arch/HYPERTWIST_PHASE6R_AI_FIRST_PARTY_PROVIDER_NEUTRAL_CUSTOM_ENDPOINT_RUNTIME_ROUTING_IMPLEMENTATION_PACKET_2026-05-26.md b/docs/arch/HYPERTWIST_PHASE6R_AI_FIRST_PARTY_PROVIDER_NEUTRAL_CUSTOM_ENDPOINT_RUNTIME_ROUTING_IMPLEMENTATION_PACKET_2026-05-26.md new file mode 100644 index 0000000..f7c2bb1 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE6R_AI_FIRST_PARTY_PROVIDER_NEUTRAL_CUSTOM_ENDPOINT_RUNTIME_ROUTING_IMPLEMENTATION_PACKET_2026-05-26.md @@ -0,0 +1,158 @@ +# HyperTwist Phase 6R-AI first-party provider-neutral custom-endpoint runtime routing implementation packet + +Created on `2026-05-26` + +## Status + +- first-party HyperTwist packet +- bounded `Phase 6R-AI` implementation slice + +## Purpose + +This packet lands the next bounded first-party slice above the landed speech +session, payload-custody, provider-profile/BYOK custody, provider-routing, +usage/cost governance, real device-permission workflow, native capture-route +workflow/control, and operator-facing native capture-route shell seams. + +The landed slice is: + +- first-party provider-neutral custom-endpoint runtime routing + +It is not: + +- a provider-specific overlay or settings-panel packet +- a managed gateway replacement packet +- an actual payment execution packet +- a provider-portal ownership packet +- an actual payload-shipping packet +- a broad assistant-platform packet + +## Current authority basis + +This implementation packet stands on: + +- `docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md` +- `docs/arch/HYPERTWIST_PHASE6R_AI_FIRST_PARTY_PROVIDER_NEUTRAL_CUSTOM_ENDPOINT_RUNTIME_ROUTING_PREPARATION_PACKET_2026-05-26.md` +- `docs/arch/HYPERTWIST_PHASE6R_X_FIRST_PARTY_PROVIDER_PROFILE_AND_BYOK_CUSTODY_IMPLEMENTATION_PACKET_2026-05-24.md` +- `docs/arch/HYPERTWIST_PHASE6R_Y_FIRST_PARTY_PROVIDER_ROUTING_AND_POLICY_IMPLEMENTATION_PACKET_2026-05-24.md` +- `docs/arch/HYPERTWIST_PHASE6R_AH_FIRST_PARTY_OPERATOR_FACING_NATIVE_CAPTURE_ROUTE_WORKFLOW_SHELL_IMPLEMENTATION_PACKET_2026-05-26.md` + +The preserved owners do not change: + +- first-party HyperTwist remains the top-level provider/session owner +- first-party HyperTwist remains the provider-profile and BYOK custody owner +- first-party HyperTwist remains the provider routing/policy owner +- first-party HyperTwist remains the bounded custom-endpoint runtime routing + owner for this slice +- `ggml-org/whisper.cpp` remains bounded to offline STT donor seams +- `SYSTRAN/faster-whisper` remains bounded to complementary Python + orchestration/service-lane seams + +## Landed scope + +The current code now owns a bounded provider-neutral custom-endpoint runtime +routing seam through: + +- retained speech-client state for: + - `LastRequestedSessionConfig` +- provider-profile endpoint and label resolution in: + - `UHyperTwistHttpSpeechClient` +- provider-profile endpoint-aware runtime routing for: + - `OpenSpeechSession(...)` + - `TranscribeSpeechUtterance(...)` + - `CloseSpeechSession(...)` + - `GetSpeechServiceHealth()` +- timeout/cancel completion safety in: + - `HyperTwistHttpSpeechClientInternal::ExecuteJsonRequest(...)` +- focused automation coverage in: + - `HyperTwistWhisperCppPhase6RAICustomEndpointRuntimeContractTest.cpp` +- retroactive bounded contract alignment in: + - `HyperTwistWhisperCppPhase6RXProviderProfileByokContractTest.cpp` + - `HyperTwistWhisperCppPhase6RYProviderRoutingPolicyContractTest.cpp` + +## Why this is still intentionally bounded + +This packet lands provider-neutral custom-endpoint runtime routing only. + +Still deferred: + +- provider-specific overlay or provider-settings ownership +- managed gateway replacement ownership +- actual payment execution or provider-portal ownership +- actual downloadable model/payload shipping +- actual OS permission-grant execution +- low-level native capture-route takeover +- broad assistant-platform scope + +## Validation + +Build validation: + +- `UnrealHyperTwistEditor Win64 Development` + +Focused automation validation: + +- `HyperTwist.Permissive.WhisperCpp.Phase6R.AI` `3/3` + +Regression automation validation: + +- `HyperTwist.Permissive.WhisperCpp.Phase6R.X.ProviderProfileServiceHealth` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.X.ProviderBackedByokFallback` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.Y.ProviderRoutingPolicyServiceHealth` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.Y.ProviderBackedRoutingFallback` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.AG.NativeCaptureRouteWorkflowPreparationState` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.AH.NativeCaptureRouteShellPreparationState` + +Non-blocking warnings stayed limited to the existing Unreal headless/editor +noise, the pre-existing plugin dependency warning on `UnrealMCP`, the headless +CEF/web-browser warning, the known `http:/vision/health` plus `http:/speech/health` +hostname-resolution warnings from existing provider-backed automation posture, +and the expected connection-refused warnings against the local provider-backed +sidecar endpoint when no live sidecar was running. + +## Queue effect + +This packet consumes the current `Phase 6R-AI` implementation slice. + +`ggml-org/whisper.cpp` remains only partially incorporated: + +- landed now: + - speech transcript session boundary + - live microphone shell boundary + - device-permission and capture-route readiness shell posture + - first-party real device-permission workflow boundary + - first-party native capture-route ownership assessment and workflow + preparation/control boundary + - first-party operator-facing native capture-route workflow shell boundary + - downloadable model and payload custody boundary + - first-party provider-profile and BYOK custody boundary + - first-party provider routing and workflow-policy boundary + - first-party provider-neutral custom-endpoint runtime routing boundary + - first-party normalized usage/cost event-model boundary + - first-party operator-facing provider usage/cost dashboard shell boundary + - first-party provider usage/cost history/export shell boundary + - first-party provider receipt review and posted-charge inspection shell + boundary + - first-party provider billing settlement and invoice reconciliation shell + boundary + - first-party provider settlement exception and external-portal handoff shell + boundary +- still deferred: + - provider-specific overlays or provider-settings ownership + - actual payment execution or provider-portal ownership + - actual OS permission-grant execution ownership + - low-level native capture-route takeover + - actual downloadable model/payload shipping + - broad assistant-platform scope + +The next clean move is not `Phase 6R-AJ` by default. + +If a later speech/provider-adjacent packet is justified, keep these guards +visible: + +- prove a narrower remaining downstream provider-neutral runtime or + operator-facing consumer gap before widening +- keep actual payment execution and provider-portal ownership separate from + any such packet +- keep actual payload shipping separate from runtime-routing and usage/cost + governance unless a narrower first-party gap is proven first diff --git a/docs/arch/HYPERTWIST_PHASE6R_AI_FIRST_PARTY_PROVIDER_NEUTRAL_CUSTOM_ENDPOINT_RUNTIME_ROUTING_PREPARATION_PACKET_2026-05-26.md b/docs/arch/HYPERTWIST_PHASE6R_AI_FIRST_PARTY_PROVIDER_NEUTRAL_CUSTOM_ENDPOINT_RUNTIME_ROUTING_PREPARATION_PACKET_2026-05-26.md new file mode 100644 index 0000000..4051d97 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE6R_AI_FIRST_PARTY_PROVIDER_NEUTRAL_CUSTOM_ENDPOINT_RUNTIME_ROUTING_PREPARATION_PACKET_2026-05-26.md @@ -0,0 +1,137 @@ +# HyperTwist Phase 6R-AI first-party provider-neutral custom-endpoint runtime routing preparation packet + +Created on `2026-05-26` + +## Status + +- first-party HyperTwist packet +- source-backed `Phase 6R-AI` preparation/control slice + +## Purpose + +This packet scopes the next bounded first-party slice above the landed speech +session, payload-custody, provider-profile/BYOK custody, provider-routing, +usage/cost governance, real device-permission workflow, native capture-route +workflow/control, and operator-facing native capture-route shell seams. + +The granted slice is: + +- first-party provider-neutral custom-endpoint runtime routing only + +It is not: + +- a provider-specific overlay or settings-panel packet +- a managed gateway replacement packet +- an actual payment execution packet +- a provider-portal ownership packet +- an actual payload-shipping packet +- a broad assistant-platform packet + +## Current authority basis + +This control pass stands on: + +- `docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md` +- `docs/arch/HYPERTWIST_PHASE6R_X_FIRST_PARTY_PROVIDER_PROFILE_AND_BYOK_CUSTODY_IMPLEMENTATION_PACKET_2026-05-24.md` +- `docs/arch/HYPERTWIST_PHASE6R_Y_FIRST_PARTY_PROVIDER_ROUTING_AND_POLICY_IMPLEMENTATION_PACKET_2026-05-24.md` +- `docs/arch/HYPERTWIST_PHASE6R_AH_FIRST_PARTY_OPERATOR_FACING_NATIVE_CAPTURE_ROUTE_WORKFLOW_SHELL_IMPLEMENTATION_PACKET_2026-05-26.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md` + +The preserved owners do not change here: + +- first-party HyperTwist remains the top-level provider/session owner +- first-party HyperTwist remains the provider-profile and BYOK custody owner +- first-party HyperTwist remains the provider routing/policy owner +- first-party HyperTwist remains the bounded custom-endpoint runtime routing + owner for this slice +- `ggml-org/whisper.cpp` remains bounded to offline STT donor seams +- `SYSTRAN/faster-whisper` remains bounded to complementary Python + orchestration/service-lane seams + +## Granted family + +The bounded `Phase 6R-AI` slice may land: + +1. per-session provider-profile endpoint resolution for provider-backed HTTP + speech session-open, utterance, close, and health flows +2. fallback health reflection that preserves the last requested provider + profile and endpoint after failed provider-backed session-open attempts +3. bounded timeout/cancel safety hardening for provider-backed HTTP delegate + completion +4. focused automation for: + - custom-endpoint session-config retention + - custom-endpoint fallback-health reflection + - direct-client custom-endpoint health reflection +5. bounded contract-alignment updates for adjacent provider-profile and + routing fallback expectations when a provider-profile endpoint exists but + the provider-backed sidecar is unavailable + +The packet must stay out of: + +- provider-specific overlay or settings-panel ownership +- actual payment execution +- provider-portal ownership +- actual payload shipping +- actual OS permission-grant execution +- low-level native capture-route takeover + +## Proposed implementation shape + +Land the narrower first-party boundary through: + +- retained speech-client state for the last requested session config +- provider-profile endpoint and label resolution in: + - `UHyperTwistHttpSpeechClient` +- per-session runtime routing for: + - speech session open + - utterance submission + - speech session close + - service-health fetch +- fallback-health routing posture that preserves: + - provider profile id + - provider profile endpoint + - provider-route decision posture + - custom-endpoint route selection state +- focused automation in: + - `HyperTwistWhisperCppPhase6RAICustomEndpointRuntimeContractTest.cpp` +- retroactive bounded contract alignment in: + - `HyperTwistWhisperCppPhase6RXProviderProfileByokContractTest.cpp` + - `HyperTwistWhisperCppPhase6RYProviderRoutingPolicyContractTest.cpp` + +## Validation target + +Validate with: + +- Unreal build for `UnrealHyperTwistEditor Win64 Development` +- focused automation: + - `HyperTwist.Permissive.WhisperCpp.Phase6R.AI.CustomEndpointSessionConfig` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.AI.CustomEndpointHealthFallback` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.AI.DirectClientHealthReflection` +- regressions: + - `HyperTwist.Permissive.WhisperCpp.Phase6R.X.ProviderProfileServiceHealth` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.X.ProviderBackedByokFallback` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.Y.ProviderRoutingPolicyServiceHealth` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.Y.ProviderBackedRoutingFallback` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.AG.NativeCaptureRouteWorkflowPreparationState` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.AH.NativeCaptureRouteShellPreparationState` + +## Queue effect + +If this packet lands cleanly, OpenAI-compatible custom-endpoint support should +no longer remain a custody-only doctrine target. A bounded first-party runtime +routing seam becomes live above the landed provider-profile/BYOK and +provider-routing lanes. + +Do not open `Phase 6R-AJ` by default after this packet. + +If a later speech/provider-adjacent first-party packet is justified, it must +prove a narrower remaining downstream provider-neutral runtime or +operator-facing consumer gap before widening into: + +- provider-specific overlays or settings ownership +- actual payment execution +- provider-portal ownership +- actual payload shipping +- actual OS permission-grant execution +- broad assistant-platform scope diff --git a/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md b/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md index 4809e0a..3a6130a 100644 --- a/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md +++ b/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md @@ -200,13 +200,17 @@ The next bounded move is now: workflow shell pass is now consumed 42. the bounded first-party `Phase 6R-AH` operator-facing native capture-route workflow shell packet is now landed in current code -43. keep the speech-lane guard visible: +43. the generic source-backed `Phase 6R-AI` first-party provider-neutral custom-endpoint runtime + routing pass is now consumed +44. the bounded first-party `Phase 6R-AI` provider-neutral custom-endpoint runtime routing + packet is now landed in current code +45. keep the speech-lane guard visible: - keep code-license judgments separate from model, voice, and payload-license review -44. keep the provider-neutral speech-lane guard visible: +46. keep the provider-neutral speech-lane guard visible: - first-party HyperTwist owns the top-level provider/session contract - `whisper.cpp` and `faster-whisper` may win narrower donor slices without inheriting that lane, the provider routing/policy lane, or the usage/cost governance lane -45. keep the `MagicTile` guard visible: +47. keep the `MagicTile` guard visible: - keep broad non-Euclidean interaction or WinForms/OpenTK host-shell ownership closed by default unless a narrower first-party gap is proven above the landed macro-remapping seam diff --git a/docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md b/docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md index 6963772..caf14c8 100644 --- a/docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md +++ b/docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md @@ -226,8 +226,14 @@ Current landed ordering status: bounded first-party implementation anchor - real device-permission workflow now also has a bounded first-party implementation anchor above the landed permission/readiness shell posture -- native capture-route ownership and workflow is now the next bounded first-party - gap above the landed readiness and permission-workflow seams +- native capture-route ownership and workflow now also has a bounded + first-party implementation anchor above the landed readiness and + permission-workflow seams +- operator-facing native capture-route workflow shell now also has a bounded + first-party implementation anchor above the landed workflow/control seam +- provider-neutral custom-endpoint runtime routing now also has a bounded + first-party implementation anchor above the landed provider-profile/BYOK and + provider-routing seams - actual payment execution or provider-portal ownership remains later first-party work - provider-specific adapters remain later work diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md index 6630211..a99e648 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md @@ -135,6 +135,7 @@ Do not collapse those three tiers into one undifferentiated "features" voice. | First-party real device-permission workflow | Implemented now | landed first-party `Phase 6R-AF` | First-party permission-workflow profiles, request/settings/recheck posture, and bounded workflow capability exposure are now live above the landed permission/readiness shell seam. The bounded first-party native capture-route workflow/control layer now lives in landed `Phase 6R-AG`. | | First-party native capture-route ownership and workflow preparation/control | Implemented now | landed first-party `Phase 6R-AG` | First-party native capture-route ownership assessment, preparation/retry/reopen posture, and bounded workflow capability exposure are now live above the landed permission-workflow seam. The bounded first-party operator-facing shell layer now lives in landed `Phase 6R-AH`. | | First-party operator-facing native capture-route workflow shell | Implemented now | landed first-party `Phase 6R-AH` | First-party shell profiles, shell-state posture, ownership-summary/preparation/session-reopen card contracts, and bounded operator-facing capability exposure are now live above the landed native capture-route workflow/control seam. Actual OS permission-grant execution, payment execution, provider-portal ownership, and payload shipping remain deferred. | +| First-party provider-neutral custom-endpoint runtime routing | Implemented now | landed first-party `Phase 6R-AI` | First-party per-session provider-profile endpoint selection, runtime routing, fallback-health reflection, and bounded provider-backed transport-failure posture are now live above the landed provider-profile/BYOK and provider-routing seams. Provider-specific overlays, payment execution, provider-portal ownership, and payload shipping remain deferred. | | Analytics/reporting surfaces | Implemented now | landed analytics/reporting packets | Reporting is real, but bounded to accepted retained slices. | | Browser/spatial/media adjunct surfaces | Implemented now | landed `three.js`, `react-three-fiber`, `xr`, `model-viewer`, `remotion` packets | These are implemented bounded families, not proof of unlimited browser-shell parity. | @@ -238,7 +239,7 @@ repo. | First-party provider receipt review and posted-charge inspection shell | Implemented now | landed first-party `Phase 6R-AC` packet | Current bounded receipt-review shell profile ids/definitions, derived receipt-review entry and issue/state posture, and capability exposure above the landed provider usage/cost history/export seam. | | First-party provider billing settlement and invoice reconciliation shell | Implemented now | landed first-party `Phase 6R-AD` packet | Current bounded settlement shell profile ids/definitions, derived settlement entry and issue/state posture, invoice-reconciliation readiness, and capability exposure above the landed provider receipt-review seam. | | First-party provider settlement exception and external-portal handoff shell | Implemented now | landed first-party `Phase 6R-AE` packet | Current bounded settlement-exception shell profile ids/definitions, derived exception entry and issue/state posture, reference-only external-portal handoff readiness, and capability exposure above the landed provider settlement seam. | -| OpenAI-compatible custom endpoint support | Deep-source grounded retained | doctrine-defined first-party target | First-class target is now represented in the landed custody model, but full routing/runtime support is still deferred. | +| First-party provider-neutral custom-endpoint runtime routing | Implemented now | landed first-party `Phase 6R-AI` packet | Current bounded per-session provider-profile endpoint selection, runtime routing, fallback-health reflection, and provider-backed transport-failure posture above the landed provider-profile/BYOK and provider-routing seams. OpenAI-compatible custom endpoints are no longer custody-only doctrine targets. | | Provider-specific overlays | Shallow placeholder | future provider-family work only | Keep internal until source-grounded and normalized. | ### 9. Memory, continuity, notes, and provenance diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md index d59f658..0d02156 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md @@ -199,11 +199,15 @@ Canonical discovery surfaces for roadmap interpretation: workflow shell pass is now consumed - the bounded first-party `Phase 6R-AH` operator-facing native capture-route workflow shell packet is now landed in current code -- the current next bounded move is not another restrictive packet by default; if a new - speech-adjacent first-party packet is justified, first prove a narrower remaining - operator-facing native capture-route consumption gap and keep actual OS permission-grant - execution, actual payment execution, provider-portal ownership, and actual payload shipping - separately deferred +- the generic source-backed `Phase 6R-AI` first-party provider-neutral custom-endpoint runtime + routing pass is now consumed +- the bounded first-party `Phase 6R-AI` provider-neutral custom-endpoint runtime routing packet + is now landed in current code +- the current next bounded move is not another speech/provider-adjacent packet by default; if a + new speech/provider-adjacent first-party packet is justified, first prove a narrower remaining + downstream provider-neutral runtime or operator-facing consumer gap and keep actual OS + permission-grant execution, actual payment execution, provider-portal ownership, and actual + payload shipping separately deferred - the repo-row implementation queue is now live from that `Phase 6R-A` entry point rather than waiting on another first-party packet @@ -395,6 +399,13 @@ Queue interpretation after that control pass: boundary - first-party provider billing settlement and invoice reconciliation shell boundary + - first-party provider settlement exception and external-portal handoff + shell boundary + - first-party real device-permission workflow boundary + - first-party native capture-route ownership and workflow + preparation/control boundary + - first-party operator-facing native capture-route workflow shell boundary + - first-party provider-neutral custom-endpoint runtime routing boundary - bounded shell capability exposure for: - microphone capture shell - device-permission shell @@ -422,9 +433,11 @@ Queue interpretation after that control pass: - provider external-portal handoff - provider settlement-exception review - still deferred: + - provider-specific overlays or provider-settings ownership - actual payment execution or provider-portal ownership - actual OS permission-grant execution ownership - - native capture-route ownership beyond the landed bounded workflow/control posture + - low-level native capture-route takeover beyond the landed bounded + workflow/control and operator-facing shell posture - actual downloadable model or payload shipping - broad assistant-platform scope - `SYSTRAN/faster-whisper` is now landed as a partially incorporated row rather than a generic @@ -470,18 +483,18 @@ Queue interpretation after that control pass: - viseme / gesture runtime integration - broad assistant-platform scope - the next queue shape is now: - - bounded first-party native capture-route ownership and workflow assessment/control above the - landed speech session, shell, permission/readiness, permission-workflow, - payload-custody, provider-custody, provider-routing, normalized usage/cost, - operator-facing dashboard, history/export, receipt-review, settlement, and - exception/handoff seams + - then continue with the retained repo-row queue + - no new speech/provider-adjacent first-party packet is justified by default - the next bounded move should stay narrow: - - if a new speech-adjacent first-party packet is justified, prove a narrower remaining - operator-facing native capture-route consumption gap above the landed permission-workflow, - native capture-route workflow/control, and operator-facing shell seams before any widening - into actual OS permission-grant execution, actual payment execution, provider-portal - ownership, actual payload shipping, broad voice-output ownership, or broad - assistant-platform scope + - if a new speech/provider-adjacent first-party packet is justified, prove a + narrower remaining downstream provider-neutral runtime or operator-facing + consumer gap above the landed provider-profile/BYOK, provider-routing, + custom-endpoint runtime, permission-workflow, native capture-route + workflow/control, and operator-facing shell seams before any widening into + actual OS permission-grant execution, actual payment execution, + provider-portal ownership, actual payload shipping, provider-specific + overlay ownership, broad voice-output ownership, or broad assistant-platform + scope - keep the speech-input / voice sidecar legal sequencing guard visible: - keep code-license judgments separate from model, voice, and payload-license review - keep the provider-neutral speech-lane guard visible: