Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions app/RealtimeTranslation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import VmWebrtcTranslatorModule, {
closeTranslationConnectionAsync,
muteUnmuteOutgoingAudio,
openTranslationConnectionAsync,
updateOutputLanguage,
type TranslationTranscriptEventPayload,
} from "../modules/vm-webrtc/src/VmWebrtcTranslatorModule";

Expand Down Expand Up @@ -378,15 +379,21 @@ export function RealtimeTranslation({
const handleSelectLanguage = useCallback((code: string) => {
setOutputLanguage(code);
saveOutputLanguage(code);
}, []);
if (isSessionActive) {
try {
updateOutputLanguage(code);
} catch (e) {
log.warn("[RealtimeTranslation] updateOutputLanguage unavailable", {}, { error: e });
}
}
}, [isSessionActive]);

const isSpeakerphone = audioOutput === "speakerphone";

return (
<View style={styles.content}>
<LanguagePickerRow
outputLanguage={outputLanguage}
isSessionActive={isSessionActive}
onSelectLanguage={handleSelectLanguage}
onOpenModal={() => setLanguageModalVisible(true)}
/>
Expand Down
11 changes: 2 additions & 9 deletions components/realtime/LanguagePickerRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ import {

type Props = {
outputLanguage: string;
isSessionActive: boolean;
onSelectLanguage: (code: string) => void;
onOpenModal: () => void;
};

export function LanguagePickerRow({
outputLanguage,
isSessionActive,
onSelectLanguage,
onOpenModal,
}: Props) {
Expand All @@ -29,11 +27,10 @@ export function LanguagePickerRow({
return (
<Pressable
key={lang.code}
onPress={() => !isSessionActive && onSelectLanguage(lang.code)}
onPress={() => onSelectLanguage(lang.code)}
style={[
styles.langChip,
isSelected && styles.langChipSelected,
isSessionActive && styles.langChipDisabled,
]}
>
<Text style={styles.langFlag}>{lang.flag}</Text>
Expand All @@ -49,11 +46,10 @@ export function LanguagePickerRow({
);
})}
<Pressable
onPress={() => !isSessionActive && onOpenModal()}
onPress={() => onOpenModal()}
style={[
styles.langChip,
styles.langChipMore,
isSessionActive && styles.langChipDisabled,
!FEATURED_LANGUAGE_CODES.includes(outputLanguage) &&
styles.langChipSelected,
]}
Expand Down Expand Up @@ -123,9 +119,6 @@ const styles = StyleSheet.create({
langChipMore: {
backgroundColor: "#F2F2F7",
},
langChipDisabled: {
opacity: 0.45,
},
langFlag: {
fontSize: 18,
},
Expand Down
17 changes: 17 additions & 0 deletions modules/vm-webrtc/ios/OpenAIWebRTCTranslatorClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,23 @@ final class OpenAIWebRTCTranslatorClient: OpenAIWebRTCBase {
return result
}

// MARK: Language update

@MainActor
func updateOutputLanguage(_ language: String) {
let trimmed = language.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return }
outputLanguage = trimmed
logger.log(
"[VmWebrtc][Translator] Sending language update",
attributes: logAttributes(for: .info, metadata: ["role": role, "language": trimmed])
)
_ = sendEvent([
"type": "session.update",
"session": ["audio": ["output": ["language": trimmed]]]
])
Comment on lines +146 to +149
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify sendEvent signature/return type and current call patterns
rg -n 'func\s+sendEvent\s*\(' modules/vm-webrtc/ios -C3
rg -n '_ = sendEvent\(' modules/vm-webrtc/ios -C2

Repository: tleyden/arty

Length of output: 2610


🏁 Script executed:

cat -n modules/vm-webrtc/ios/WebRtcClientHelpers.swift | sed -n '647,680p'

Repository: tleyden/arty

Length of output: 1807


🏁 Script executed:

# Check if there are any places where sendEvent result IS used
rg 'sendEvent\(' modules/vm-webrtc/ios -B2 -A2 | grep -v '_ = sendEvent'

Repository: tleyden/arty

Length of output: 7087


Add explicit failure handling for session.update with call-site context.

sendEvent() returns a Bool indicating success, but line 146 ignores it via _ =. Although sendEvent() logs internally, it lacks the operation-specific context (role, language) needed for observability. Similar critical operations elsewhere in the codebase (e.g., function call outputs, response.create in OpenAIWebRTCClient.swift) explicitly check the result and log context—this language update should follow the same pattern to prevent silent state desync.

Suggested fix
-        _ = sendEvent([
+        let didSend = sendEvent([
             "type": "session.update",
             "session": ["audio": ["output": ["language": trimmed]]]
         ])
+        if !didSend {
+            logger.log(
+                "[VmWebrtc][Translator] Failed to send language update",
+                attributes: logAttributes(for: .warn, metadata: ["role": role, "language": trimmed])
+            )
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/vm-webrtc/ios/OpenAIWebRTCTranslatorClient.swift` around lines 146 -
149, Capture and check the Bool returned by sendEvent(...) for the
"session.update" call in OpenAIWebRTCTranslatorClient (currently invoked with
the session payload containing ["audio": ["output": ["language": trimmed]]]); if
the result is false, emit an explicit error log using the existing logger (e.g.,
processLogger.error or the class's logger) that includes operation-specific
context such as "session.update", the role/caller (if available at the call
site), and the trimmed language value, and then take an appropriate local action
(e.g., return/fail fast or notify caller) to avoid silent state desync.

}

// MARK: Virtual hook overrides

override func dataChannelDidOpen() {
Expand Down
6 changes: 6 additions & 0 deletions modules/vm-webrtc/ios/WebRTCTranslatorModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,11 @@ public class WebRTCTranslatorModule: Module {
self.reverseClient.setOutgoingAudioMuted(shouldMute)
}
}

Function("updateOutputLanguage") { (language: String) in
Task { @MainActor in
self.primaryClient.updateOutputLanguage(language)
}
}
}
}
7 changes: 7 additions & 0 deletions modules/vm-webrtc/src/VmWebrtcTranslatorModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ declare class VmWebrtcTranslatorModule extends NativeModule<TranslatorModuleEven
): Promise<OpenAIConnectionState>;
closeTranslationConnectionAsync(): Promise<OpenAIConnectionState>;
muteUnmuteOutgoingAudio(shouldMute: boolean): void;
updateOutputLanguage(language: string): void;
}

const makeUnavailableError = () =>
Expand Down Expand Up @@ -84,4 +85,10 @@ export const muteUnmuteOutgoingAudio = (shouldMute: boolean): void => {
module.muteUnmuteOutgoingAudio(shouldMute);
};

export const updateOutputLanguage = (language: string): void => {
if (!module) throw makeUnavailableError();
log.debug(`[${MODULE_NAME}] updateOutputLanguage`, {}, { language });
module.updateOutputLanguage(language);
};

export default module ?? ({} as VmWebrtcTranslatorModule);
Loading