Skip to content

Commit 7da5764

Browse files
authored
fix(voice): migrate OpenAI Realtime to GA API + make agent .env authoritative (#46)
Voice mode was fully broken: OpenAI shut off the Realtime Beta API, and a stray placeholder env var could shadow the real key. OpenAI Realtime GA migration (src/voice/openai-realtime.ts): - Drop the deprecated `OpenAI-Beta: realtime=v1` header (the server now rejects it: 'The Realtime Beta API is no longer supported'). - session.update converted to GA shape: session.type='realtime', output_modalities, nested audio.input/audio.output (format + voice + turn_detection + transcription). - Handle GA event names response.output_audio[_transcript].* alongside the old beta names. .env precedence (src/index.ts, src/voice/server.ts): - Agent-dir .env now OVERRIDES inherited env instead of skipping keys that are already set. A placeholder like OPENAI_API_KEY="your-...-here" in a shell profile was silently beating the real key in the agent .env, which surfaced to users as the misleading 'no API key set' message. - index.ts loader also strips surrounding quotes and skips comments/blanks. Bumps to 1.5.1. Verified locally: adapter connects, session created + configured, no 'Voice mode unavailable'.
1 parent a9c65d0 commit 7da5764

5 files changed

Lines changed: 36 additions & 23 deletions

File tree

‎package-lock.json‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@open-gitagent/gitagent",
3-
"version": "1.5.0",
3+
"version": "1.5.1",
44
"description": "A universal git-native multimodal always learning AI Agent (TinyHuman)",
55
"author": "shreyaskapale",
66
"license": "MIT",

‎src/index.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -379,14 +379,18 @@ async function main(): Promise<void> {
379379
const envPath = resolve(dir, ".env");
380380
if (existsSync(envPath)) {
381381
const envContent = readFileSync(envPath, "utf-8");
382-
for (const line of envContent.split("\n")) {
382+
for (const rawLine of envContent.split("\n")) {
383+
const line = rawLine.trim();
384+
if (!line || line.startsWith("#")) continue;
383385
const eq = line.indexOf("=");
384386
if (eq <= 0) continue;
385387
const key = line.slice(0, eq).trim();
386-
const val = line.slice(eq + 1).trim();
387-
if (!process.env[key]) {
388-
process.env[key] = val;
388+
let val = line.slice(eq + 1).trim();
389+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
390+
val = val.slice(1, -1);
389391
}
392+
// Agent .env wins over inherited env (e.g. a placeholder OPENAI_API_KEY in ~/.zshrc).
393+
process.env[key] = val;
390394
}
391395
}
392396

‎src/voice/openai-realtime.ts‎

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ export class OpenAIRealtimeAdapter implements MultimodalAdapter {
4444
await this.connectWs(url, {
4545
headers: {
4646
Authorization: `Bearer ${this.config.apiKey}`,
47-
"OpenAI-Beta": "realtime=v1",
4847
},
4948
});
5049
return;
@@ -83,7 +82,6 @@ export class OpenAIRealtimeAdapter implements MultimodalAdapter {
8382
await this.connectWs(url, {
8483
headers: {
8584
Authorization: `Bearer ${ephemeralKey}`,
86-
"OpenAI-Beta": "realtime=v1",
8785
},
8886
});
8987
}
@@ -147,17 +145,26 @@ export class OpenAIRealtimeAdapter implements MultimodalAdapter {
147145
const payload = {
148146
type: "session.update",
149147
session: {
148+
type: "realtime",
149+
output_modalities: ["audio"],
150150
instructions,
151-
voice: this.config.voice || "ash",
152-
modalities: ["text", "audio"],
153-
turn_detection: {
154-
type: "server_vad",
155-
threshold: 0.6,
156-
prefix_padding_ms: 400,
157-
silence_duration_ms: 800,
158-
create_response: true,
151+
audio: {
152+
input: {
153+
format: { type: "audio/pcm", rate: 24000 },
154+
turn_detection: {
155+
type: "server_vad",
156+
threshold: 0.6,
157+
prefix_padding_ms: 400,
158+
silence_duration_ms: 800,
159+
create_response: true,
160+
},
161+
transcription: { model: "whisper-1" },
162+
},
163+
output: {
164+
format: { type: "audio/pcm", rate: 24000 },
165+
voice: this.config.voice || "ash",
166+
},
159167
},
160-
input_audio_transcription: { model: "whisper-1" },
161168
tool_choice: "auto",
162169
tools: [
163170
{
@@ -285,7 +292,6 @@ export class OpenAIRealtimeAdapter implements MultimodalAdapter {
285292
await this.connectWs(url, {
286293
headers: {
287294
Authorization: `Bearer ${this.config.apiKey}`,
288-
"OpenAI-Beta": "realtime=v1",
289295
},
290296
});
291297
} catch (err: any) {
@@ -302,7 +308,6 @@ export class OpenAIRealtimeAdapter implements MultimodalAdapter {
302308
const ephemeralKey = session.client_secret?.value;
303309
if (!ephemeralKey) throw new Error("No ephemeral key on refresh");
304310
await this.connectWs(url, {
305-
headers: { Authorization: `Bearer ${ephemeralKey}`, "OpenAI-Beta": "realtime=v1" },
306311
});
307312
}
308313
console.log(dim("[voice] Session refreshed"));
@@ -388,17 +393,21 @@ export class OpenAIRealtimeAdapter implements MultimodalAdapter {
388393
this.interrupted = false;
389394
break;
390395

396+
// GA event names are response.output_audio*; keep the beta aliases too.
391397
case "response.audio.delta":
398+
case "response.output_audio.delta":
392399
if (event.delta && !this.interrupted) {
393400
this.emit({ type: "audio_delta", audio: event.delta });
394401
}
395402
break;
396403

397404
case "response.audio_transcript.delta":
405+
case "response.output_audio_transcript.delta":
398406
this.emit({ type: "transcript", role: "assistant", text: event.delta || "", partial: true });
399407
break;
400408

401409
case "response.audio_transcript.done":
410+
case "response.output_audio_transcript.done":
402411
if (event.transcript) {
403412
this.emit({ type: "transcript", role: "assistant", text: event.transcript });
404413
}

‎src/voice/server.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -595,9 +595,9 @@ function loadEnvFile(dir: string) {
595595
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
596596
val = val.slice(1, -1);
597597
}
598-
if (!process.env[key]) {
599-
process.env[key] = val;
600-
}
598+
// Agent .env takes precedence over inherited env (e.g. shell placeholders
599+
// like a stray OPENAI_API_KEY="your-...-here" in ~/.zshrc).
600+
process.env[key] = val;
601601
}
602602
} catch {
603603
// No .env file — that's fine

0 commit comments

Comments
 (0)