Implement Phase 6R-AI custom-endpoint runtime routing
This commit is contained in:
parent
5403a35af1
commit
bfff2d310d
11 changed files with 833 additions and 67 deletions
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -95,5 +95,8 @@ private:
|
|||
UPROPERTY()
|
||||
TMap<FString, FHyperTwistSpeechSessionConfig> SessionConfigs;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistSpeechSessionConfig LastRequestedSessionConfig;
|
||||
|
||||
mutable FString LastTransportError;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue