fix(talk): preserve realtime provider SDK contract

Punchcard-Session: golden-meadow-cedar-dv
This commit is contained in:
Vincent Koc
2026-08-11 23:51:02 +08:00
parent 610bd0bb95
commit 65429d097b
19 changed files with 58 additions and 48 deletions
@@ -1 +1 @@
{"contentHash":"db186d810ff456142338c36af49cf272d4d58063d341a9d2f93885acc1c0a168","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"}
{"contentHash":"39d48898b10f6d4ef6b2dbb1fbb3ae8e096d0ee76a212d31d91e4de0ea398566","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"30bff907731a9c485e5ebf857d2bccb22f64733d6db696f65630ed24ffb0ab9d","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"}
{"contentHash":"caba2cdff925b864544cfa9ad30a50d36aa2e3d02eec2a3b82329fe80abe73c4","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"354bdde4f8741dc12786458920d1c1f251d6771f86e1b35455388fcffad7412e","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"}
{"contentHash":"bd0ca8f5fceab0afd12595510947e8e03836a987131808d41968648ca27ca0a7","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"}
@@ -1 +1 @@
{"contentHash":"e758c281a2468ed7b6692d7aac41532aa392199205d34c21425885650fe5002a","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"}
{"contentHash":"7de6b8aa7aaa563081de41601fa5b1dbafdcf1db4f0619efdfc0e665a18200bf","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"}
@@ -1 +1 @@
{"contentHash":"ffdbd5eee1c1d4c3e3a906e9ef11cac1b67594c3f28698fbb91c9b4e9cefef01","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"}
{"contentHash":"878b20eb69916c437adb781ea65c01ebb1368a2b588392848f31339c44640ec6","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"}
@@ -1 +1 @@
{"contentHash":"e32f1483f2008c9ba937a6190e632ed3d47c59f9403770cad5ec4079c66f6e67","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"}
{"contentHash":"9ccff6b92eb45510ddbd80647ba47c965ef40928c566bbca8931f49942a85b42","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"}
@@ -1 +1 @@
{"contentHash":"4b024e2a19bf93fbe9c910d84d9960751b89c287e69f7d56fc85ccae4e39e3ba","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"}
{"contentHash":"9cddc6986f6241e736d9db7429d55201d3476aea67a9790499e7d4ae1c55f595","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"a12c5a0cb965b393e90521aeb1ce23d9939d9468935a33d8c7c3e717f822d10a","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"}
{"contentHash":"ea89b6eb15a02e04a68c487c0d0de783cf2081291168ac3fc208487288912071","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"18eac1e06d9ac32918f30178967692e13a47d7272b30c4386d12b74366192a47","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"}
{"contentHash":"acdda0f0f91c57963a22b378bcade2eeaea8c783ef37e4659a70331aed54e5ee","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"}
@@ -1 +1 @@
{"contentHash":"d47afcf53c6c5d9ad1cc99772d09ae60f447824583ab66e0a521f8230fec7bf3","entrypoint":"gateway-runtime","importSpecifier":"openclaw/plugin-sdk/gateway-runtime"}
{"contentHash":"76f94d3629cc7d90118003f29ed95e541ea86919e23eb242eb2b3358a0d4551e","entrypoint":"gateway-runtime","importSpecifier":"openclaw/plugin-sdk/gateway-runtime"}
@@ -1 +1 @@
{"contentHash":"6949fac4c91bb514789686bf0808d4255c1c9af27e0b880c65be2f25e20a4ec1","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"}
{"contentHash":"b54392fac15cfcd68e1484b8de2d99cf3958bc9114dfd6dc7da9ed9978e877b3","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"}
@@ -1 +1 @@
{"contentHash":"57a1b7494090393e3c88294bf717a91ce894a059af4ef940cffe110ed4567a47","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"}
{"contentHash":"cd6714e5cb63e6efe2ca4d22d80f6e13e7abb9309712c002f9309c4bbf82c7bb","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"606c0eefefe6c30667c337de3a7222bbd1f03ab2c87703445407aa48e651895a","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"}
{"contentHash":"71c3623042aeb01cba967f3f81d1eeb456e2a7e83f2626cdd4ac504c533785a7","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"ef25098b086e8959a75cd35962a000ab2e564aa4c57d0987cb991532e623b509","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"}
{"contentHash":"737a29703c4f0d005399a7526856f467c6072416362bcac76ef706d9f960961e","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"}
@@ -1 +1 @@
{"contentHash":"d2642457b2c68f9a0c7b4b46da3ad1f960b1358f235e07da83ec2b121c4897f3","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"}
{"contentHash":"273bbe50ef416560d01e717ad0593c5e513ad6a1cfb9378edd986f6403bb2ded","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"56b152bbedcc3c35b9f8ab1e2d44d999931aa24f88b1b902948803f08c8899fe","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"}
{"contentHash":"81dbaf3b8ab62e6b7a132ff01aeb2ec9ab1fcda22b0e089d28fe681d6129c77c","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"}
@@ -1 +1 @@
{"contentHash":"1e5dfd14f428c5181b3f59a6d793d3688cefa04ed4fbde992fdfd7ee9c2bb84c","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"}
{"contentHash":"fe5816b43499ea9edb0687f1e133fdb6986d39b8eaf4d6aa08febfa871a839fe","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"}
+5
View File
@@ -865,6 +865,11 @@ catalog, API-key auth, and dynamic model resolution.
clients. Implement `handleBargeIn` when a transport can detect that a
human is interrupting assistant playback and the provider supports
truncating or clearing the active audio response.
`handleBargeIn(options)` may provide a request-scoped
`options.onClearAudio` callback for the exact output being cancelled.
Use that override for the current call; when it is absent, fall back to
the session's `onClearAudio` callback supplied when the bridge was
created.
`submitToolResult` may return `void` for synchronous submission, or a
`Promise<void>` for an asynchronous completion boundary the provider
bridge can expose. Gateway relay sessions wait for that promise before
+36 -31
View File
@@ -127,7 +127,7 @@ type RealtimeVoiceOutputAudioEvents = {
turn: TalkEnsureTurnResult;
};
type RealtimeVoiceSessionHarnessBase<TForcedConsultContext> = {
export type RealtimeVoiceSessionHarness<TForcedConsultContext = unknown> = {
readonly forcedConsults: RealtimeVoiceForcedConsultCoordinator<TForcedConsultContext>;
readonly outputActivity: RealtimeVoiceOutputActivityTracker;
readonly talk: TalkSessionController;
@@ -136,7 +136,10 @@ type RealtimeVoiceSessionHarnessBase<TForcedConsultContext> = {
close(): void;
createBridge(params: RealtimeVoiceBridgeSessionParams): RealtimeVoiceBridgeSession;
emit<TPayload>(input: TalkEventInput<TPayload>): TalkEvent<TPayload>;
ensureTurn(): string;
endTurn(reason?: string): void;
finishResponse(outcome: RealtimeVoiceResponseOutcome): TalkTurnResult;
finishOutputAudio(reason: string): void;
flushOutput(flush: () => void): void;
getHealth(params: {
providerConnected: boolean;
@@ -145,15 +148,9 @@ type RealtimeVoiceSessionHarnessBase<TForcedConsultContext> = {
handleBargeIn(options: RealtimeVoiceBargeInOptions, flushOutput: () => void): void;
isLikelyAssistantEchoTranscript(text: string): boolean;
isOutputPlaybackWindowActive(): boolean;
recordTranscript(role: RealtimeVoiceRole, text: string): RealtimeVoiceTranscriptEntry;
};
type RealtimeVoiceSessionHarnessMethods = {
ensureTurn(): string;
endTurn(reason?: string): void;
finishOutputAudio(reason: string): void;
recordInputAudio(audio: Buffer): boolean;
recordOutputAudio(audio: Buffer, activity?: RealtimeVoiceOutputActivityDelta): void;
recordTranscript(role: RealtimeVoiceRole, text: string): RealtimeVoiceTranscriptEntry;
};
type RealtimeVoiceEventCapturingSessionHarnessMethods = {
@@ -170,27 +167,28 @@ type RealtimeVoiceEventCapturingSessionHarnessMethods = {
): RealtimeVoiceOutputAudioEvents;
};
export type RealtimeVoiceSessionHarness<TForcedConsultContext = unknown> =
RealtimeVoiceSessionHarnessBase<TForcedConsultContext> & RealtimeVoiceSessionHarnessMethods;
export type RealtimeVoiceEventCapturingSessionHarness<TForcedConsultContext = unknown> = Omit<
RealtimeVoiceSessionHarness<TForcedConsultContext>,
"ensureTurn" | "endTurn" | "finishOutputAudio" | "recordInputAudio" | "recordOutputAudio"
> &
RealtimeVoiceEventCapturingSessionHarnessMethods;
export type RealtimeVoiceEventCapturingSessionHarness<TForcedConsultContext = unknown> =
RealtimeVoiceSessionHarnessBase<TForcedConsultContext> &
RealtimeVoiceEventCapturingSessionHarnessMethods;
type RealtimeVoiceSessionHarnessImplementation<TForcedConsultContext> =
RealtimeVoiceSessionHarnessBase<TForcedConsultContext> & {
ensureTurn(): string | TalkEnsureTurnResult;
endTurn(reason?: string): TalkTurnResult | undefined;
finishOutputAudio(
reason: string,
details?: RealtimeVoiceOutputAudioDoneDetails,
): TalkEvent | undefined;
recordInputAudio(audio: Buffer): boolean | RealtimeVoiceInputAudioEvents | undefined;
recordOutputAudio(
audio: Buffer,
activity?: RealtimeVoiceOutputActivityDelta,
): RealtimeVoiceOutputAudioEvents | undefined;
};
type RealtimeVoiceSessionHarnessImplementation<TForcedConsultContext> = Omit<
RealtimeVoiceSessionHarness<TForcedConsultContext>,
"ensureTurn" | "endTurn" | "finishOutputAudio" | "recordInputAudio" | "recordOutputAudio"
> & {
ensureTurn(): string | TalkEnsureTurnResult;
endTurn(reason?: string): TalkTurnResult | undefined;
finishOutputAudio(
reason: string,
details?: RealtimeVoiceOutputAudioDoneDetails,
): TalkEvent | undefined;
recordInputAudio(audio: Buffer): boolean | RealtimeVoiceInputAudioEvents | undefined;
recordOutputAudio(
audio: Buffer,
activity?: RealtimeVoiceOutputActivityDelta,
): RealtimeVoiceOutputAudioEvents | undefined;
};
type RealtimeVoiceSessionHarnessParams = {
talk: TalkSessionControllerParams;
@@ -210,9 +208,16 @@ type RealtimeVoiceEventCapturingSessionHarnessParams = Omit<
talkPayloads: RealtimeVoiceEventCapturingTalkPayloads;
};
export function createRealtimeVoiceSessionHarness<TForcedConsultContext = unknown>(
params: RealtimeVoiceSessionHarnessParams,
): RealtimeVoiceSessionHarness<TForcedConsultContext> {
export function createRealtimeVoiceSessionHarness<TForcedConsultContext = unknown>(params: {
talk: TalkSessionControllerParams;
talkPayloads: RealtimeVoiceSessionHarnessTalkPayloads;
onTalkEvent?: (event: TalkEvent) => void;
talkback?: Omit<RealtimeVoiceAgentTalkbackQueueParams, "isStopped">;
forcedConsults?: RealtimeVoiceForcedConsultCoordinatorOptions;
echoSuppression?: RealtimeVoiceSessionHarnessEchoSuppression;
transcriptLookbackMs?: number;
captureBridgeEvents?: boolean;
}): RealtimeVoiceSessionHarness<TForcedConsultContext> {
return createRealtimeVoiceSessionHarnessImplementation(
params,
false,