Implement Phase 6R-AI custom-endpoint runtime routing

This commit is contained in:
axiomlogicnexus 2026-05-26 23:31:20 +02:00
parent 5403a35af1
commit bfff2d310d
11 changed files with 833 additions and 67 deletions

View file

@ -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<FHttpJsonResponse, ESPMode::ThreadSafe> ResponseState =
MakeShared<FHttpJsonResponse, ESPMode::ThreadSafe>();
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<double>(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");

View file

@ -95,5 +95,8 @@ private:
UPROPERTY()
TMap<FString, FHyperTwistSpeechSessionConfig> SessionConfigs;
UPROPERTY()
FHyperTwistSpeechSessionConfig LastRequestedSessionConfig;
mutable FString LastTransportError;
};

View file

@ -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<FStrProperty>(
UHyperTwistTrainingSubsystem::StaticClass(),
TEXT("CompanionSpeechClientKind")
))
{
SpeechClientKindProperty->SetPropertyValue_InContainer(TrainingSubsystem, ClientKind);
}
}
UHyperTwistTrainingSubsystem* MakeSpeechSubsystem(const FString& SessionId)
{
UGameInstance* GameInstance = NewObject<UGameInstance>(GetTransientPackage());
if (GameInstance == nullptr)
{
return nullptr;
}
UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject<UHyperTwistTrainingSubsystem>(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<FStructProperty>(
UHyperTwistTrainingSubsystem::StaticClass(),
TEXT("ActiveCompanionSpeechSessionState")
))
{
return SessionStateProperty->ContainerPtrToValuePtr<FHyperTwistTrainingCompanionSpeechSessionState>(
TrainingSubsystem
);
}
return nullptr;
}
UHyperTwistHttpSpeechClient* ResolveHttpSpeechClient(UHyperTwistTrainingSubsystem* TrainingSubsystem)
{
if (TrainingSubsystem == nullptr)
{
return nullptr;
}
FString CloseError;
TrainingSubsystem->CloseActiveCompanionSpeechSession(CloseError);
if (FObjectPropertyBase* ClientProperty = FindFProperty<FObjectPropertyBase>(
UHyperTwistTrainingSubsystem::StaticClass(),
TEXT("ActiveCompanionSpeechClientObject")
))
{
return Cast<UHyperTwistHttpSpeechClient>(
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

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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: