voice-vibecoder 2.20.3__tar.gz → 2.22.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/CHANGELOG.md +16 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/PKG-INFO +1 -1
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/pyproject.toml +1 -1
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/__init__.py +1 -1
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/config.py +5 -3
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/session.py +98 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/voice_providers/__init__.py +3 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/voice_providers/openai.py +75 -38
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/.github/workflows/ci.yml +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/.github/workflows/release.yml +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/.gitignore +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/LICENSE +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/README.md +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/app.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/index.html +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/app_config.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/audio.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/cli_serve.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/code_providers/__init__.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/code_providers/claude.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/code_providers/cursor.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/code_providers/openclaw.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/code_providers/registry.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/instances.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/server.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/tools/__init__.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/tools/agent_task.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/tools/definitions.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/tools/dispatch.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/tools/file_tree.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/tools/handlers.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/ui/__init__.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/ui/callbacks.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/ui/diff_view.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/ui/help_modal.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/ui/panels.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/ui/screen.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/ui/setup.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/ui/state.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/ui/styles.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/ui/update_modal.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/ui/wizard.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/voice_providers/gemini.py +0 -0
- {voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/worktrees.py +0 -0
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v2.22.0 (2026-06-30)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **realtime**: Support the Azure GA realtime API (gpt-realtime / gpt-realtime-2)
|
|
10
|
+
([`93703c3`](https://github.com/snokam/voice-vibecoder/commit/93703c322410e882a796b9415d663a3c446398c0))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## v2.21.0 (2026-04-12)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
- **session**: Narrate background tool events to voice user
|
|
18
|
+
([`9c28a4f`](https://github.com/snokam/voice-vibecoder/commit/9c28a4f2f6a3f2bc6e6243f11a629ab0e369d7ee))
|
|
19
|
+
|
|
20
|
+
|
|
5
21
|
## v2.20.3 (2026-02-23)
|
|
6
22
|
|
|
7
23
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: voice-vibecoder
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.22.0
|
|
4
4
|
Summary: Olaf The Vibecoder — a voice-controlled coding assistant using OpenAI Realtime API + Claude Code
|
|
5
5
|
Project-URL: Homepage, https://github.com/snokam/voice-vibecoder
|
|
6
6
|
Project-URL: Repository, https://github.com/snokam/voice-vibecoder
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "voice-vibecoder"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.22.0"
|
|
8
8
|
description = "Olaf The Vibecoder — a voice-controlled coding assistant using OpenAI Realtime API + Claude Code"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -354,10 +354,12 @@ class RealtimeSettings:
|
|
|
354
354
|
@property
|
|
355
355
|
def ws_url(self) -> str:
|
|
356
356
|
if self.provider == "azure":
|
|
357
|
+
# GA realtime API: /openai/v1/realtime?model=<deployment>, no
|
|
358
|
+
# api-version. The dated preview api-versions reject GA-only models
|
|
359
|
+
# (e.g. gpt-realtime / gpt-realtime-2 → "only available on the GA API").
|
|
357
360
|
return (
|
|
358
|
-
f"wss://{self.azure.endpoint}/openai/realtime"
|
|
359
|
-
f"?
|
|
360
|
-
f"&deployment={self.azure.deployment}"
|
|
361
|
+
f"wss://{self.azure.endpoint}/openai/v1/realtime"
|
|
362
|
+
f"?model={self.azure.deployment}"
|
|
361
363
|
)
|
|
362
364
|
if self.provider == "gemini":
|
|
363
365
|
return f"{GEMINI_WS_URL}?key={self.active_api_key}"
|
|
@@ -13,6 +13,7 @@ via the voice provider so ChatGPT can summarize it to the user.
|
|
|
13
13
|
|
|
14
14
|
import asyncio
|
|
15
15
|
import logging
|
|
16
|
+
import time
|
|
16
17
|
from pathlib import Path
|
|
17
18
|
from typing import Any, Callable
|
|
18
19
|
|
|
@@ -49,6 +50,70 @@ logger = logging.getLogger(__name__)
|
|
|
49
50
|
RECONNECT_MAX_ATTEMPTS = 5
|
|
50
51
|
RECONNECT_DELAYS = [2, 4, 8, 16, 30]
|
|
51
52
|
|
|
53
|
+
# Debounce between spoken tool-progress narrations. Prevents the model from
|
|
54
|
+
# interrupting itself mid-filler when many tool events fire in quick succession.
|
|
55
|
+
TOOL_NARRATION_DEBOUNCE_SEC = 6.0
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Markers used by code-provider runners to tag the kind of tool event:
|
|
59
|
+
# 🔧 tool invocation (tool_call)
|
|
60
|
+
# ✓ tool completed successfully (tool_result)
|
|
61
|
+
# ❌ tool failed (tool_result)
|
|
62
|
+
# ↳ streaming output preview, NOT a completion — skip narration
|
|
63
|
+
_TOOL_CALL_MARKER = "\U0001f527" # 🔧
|
|
64
|
+
_TOOL_OK_MARKER = "\u2713" # ✓
|
|
65
|
+
_TOOL_ERR_MARKER = "\u274c" # ❌
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _friendly_tool_hint(line: str) -> str | None:
|
|
69
|
+
"""Turn a registry tool-event line into a short natural-language hint.
|
|
70
|
+
|
|
71
|
+
Lines look like `"tool_call:🔧 jira_search({...})"` or
|
|
72
|
+
`"tool_result:✓ jira_search: 3 issues"`. Returns None for lines that
|
|
73
|
+
aren't recognized tool starts/completions (e.g. streaming previews)
|
|
74
|
+
so the caller skips narration.
|
|
75
|
+
"""
|
|
76
|
+
if not line or ":" not in line:
|
|
77
|
+
return None
|
|
78
|
+
category, rest = line.split(":", 1)
|
|
79
|
+
if category not in ("tool_call", "tool_result"):
|
|
80
|
+
return None
|
|
81
|
+
rest = rest.strip()
|
|
82
|
+
# Must be one of the known markers — otherwise it's a preview or
|
|
83
|
+
# something we don't want to narrate.
|
|
84
|
+
if category == "tool_call":
|
|
85
|
+
if not rest.startswith(_TOOL_CALL_MARKER):
|
|
86
|
+
return None
|
|
87
|
+
verb = "using"
|
|
88
|
+
else:
|
|
89
|
+
if rest.startswith(_TOOL_OK_MARKER):
|
|
90
|
+
verb = "finished"
|
|
91
|
+
elif rest.startswith(_TOOL_ERR_MARKER):
|
|
92
|
+
verb = "had trouble with"
|
|
93
|
+
else:
|
|
94
|
+
return None
|
|
95
|
+
rest = rest[1:].lstrip()
|
|
96
|
+
for sep in ("(", ":", " "):
|
|
97
|
+
if sep in rest:
|
|
98
|
+
rest = rest.split(sep, 1)[0]
|
|
99
|
+
raw = rest.strip()
|
|
100
|
+
if not raw:
|
|
101
|
+
return None
|
|
102
|
+
name = raw.split(".", 1)[-1].replace("_", " ").replace("-", " ").strip()
|
|
103
|
+
if not name:
|
|
104
|
+
return None
|
|
105
|
+
if verb == "using":
|
|
106
|
+
return f"using {name}"
|
|
107
|
+
if verb == "finished":
|
|
108
|
+
return f"finished {name}, preparing answer"
|
|
109
|
+
return f"had trouble with {name}, trying another way"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
_TOOL_NARRATION_PROMPT = (
|
|
113
|
+
"[agent background status: {hint}. Let the user know naturally in "
|
|
114
|
+
"whatever language they are speaking. Don't guess at the results.]"
|
|
115
|
+
)
|
|
116
|
+
|
|
52
117
|
|
|
53
118
|
class RealtimeSession:
|
|
54
119
|
"""Manages a single voice session with multi-instance support."""
|
|
@@ -89,6 +154,7 @@ class RealtimeSession:
|
|
|
89
154
|
self._response_pending = False
|
|
90
155
|
self._fallback_task: asyncio.Task | None = None
|
|
91
156
|
self._loop: asyncio.AbstractEventLoop | None = None
|
|
157
|
+
self._last_tool_narration: float = 0.0
|
|
92
158
|
|
|
93
159
|
# Create instance registry and wire callbacks
|
|
94
160
|
self._registry = InstanceRegistry(user_email=user_email, user_jwt=user_jwt)
|
|
@@ -322,6 +388,38 @@ class RealtimeSession:
|
|
|
322
388
|
def _on_registry_output(self, instance_id: str, line: str) -> None:
|
|
323
389
|
if self._on_agent_output:
|
|
324
390
|
self._on_agent_output(instance_id, line)
|
|
391
|
+
self._maybe_narrate_tool_event(line)
|
|
392
|
+
|
|
393
|
+
def _maybe_narrate_tool_event(self, line: str) -> None:
|
|
394
|
+
"""Speak a short filler phrase when a background tool event fires.
|
|
395
|
+
|
|
396
|
+
Without this, a caller hears 30–60s of silence while the agent runs
|
|
397
|
+
MCP tools. Debounced to TOOL_NARRATION_DEBOUNCE_SEC so many rapid
|
|
398
|
+
tool events coalesce into at most one spoken hint. Called from
|
|
399
|
+
background threads, so the injection is bounced to the session's
|
|
400
|
+
asyncio loop.
|
|
401
|
+
"""
|
|
402
|
+
if self.text_only or not self._running:
|
|
403
|
+
return
|
|
404
|
+
if not isinstance(line, str):
|
|
405
|
+
return
|
|
406
|
+
hint = _friendly_tool_hint(line)
|
|
407
|
+
if not hint:
|
|
408
|
+
return
|
|
409
|
+
now = time.monotonic()
|
|
410
|
+
if (now - self._last_tool_narration) < TOOL_NARRATION_DEBOUNCE_SEC:
|
|
411
|
+
return
|
|
412
|
+
loop = self._loop
|
|
413
|
+
if loop is None:
|
|
414
|
+
return
|
|
415
|
+
self._last_tool_narration = now
|
|
416
|
+
text = _TOOL_NARRATION_PROMPT.format(hint=hint)
|
|
417
|
+
try:
|
|
418
|
+
asyncio.run_coroutine_threadsafe(
|
|
419
|
+
self._provider.inject_message(text), loop,
|
|
420
|
+
)
|
|
421
|
+
except Exception as e:
|
|
422
|
+
logger.warning("Tool narration inject failed: %s", e)
|
|
325
423
|
|
|
326
424
|
def _on_registry_status_change(self, instance_id: str, status: InstanceStatus) -> None:
|
|
327
425
|
if self._on_instance_status:
|
{voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/voice_providers/__init__.py
RENAMED
|
@@ -142,4 +142,7 @@ def create_provider(settings) -> VoiceProvider:
|
|
|
142
142
|
return OpenAIVoiceProvider(
|
|
143
143
|
ws_url=settings.ws_url,
|
|
144
144
|
ws_headers=settings.ws_headers,
|
|
145
|
+
# Azure realtime is the GA API (/openai/v1/realtime); it uses the nested
|
|
146
|
+
# session schema and response.output_audio.* events.
|
|
147
|
+
ga=settings.provider == "azure",
|
|
145
148
|
)
|
{voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/voice_providers/openai.py
RENAMED
|
@@ -41,12 +41,69 @@ class OpenAIVoiceProvider:
|
|
|
41
41
|
ws_url: str,
|
|
42
42
|
ws_headers: dict[str, str],
|
|
43
43
|
transcription_model: str = "gpt-4o-mini-transcribe",
|
|
44
|
+
ga: bool = False,
|
|
44
45
|
) -> None:
|
|
45
46
|
self._ws_url = ws_url
|
|
46
47
|
self._ws_headers = ws_headers
|
|
47
48
|
self._transcription_model = transcription_model
|
|
49
|
+
# GA realtime API (Azure /openai/v1/realtime): nested `audio` session
|
|
50
|
+
# schema + response.output_audio.* events. Preview otherwise.
|
|
51
|
+
self._ga = ga
|
|
48
52
|
self._ws: Any = None
|
|
49
53
|
|
|
54
|
+
def _turn_detection(self, vad: VADSettings | None) -> dict | None:
|
|
55
|
+
if vad is None:
|
|
56
|
+
return None
|
|
57
|
+
return {
|
|
58
|
+
"type": "server_vad",
|
|
59
|
+
"threshold": vad.threshold,
|
|
60
|
+
"prefix_padding_ms": vad.prefix_padding_ms,
|
|
61
|
+
"silence_duration_ms": vad.silence_duration_ms,
|
|
62
|
+
"create_response": True,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
def _session_body(
|
|
66
|
+
self,
|
|
67
|
+
instructions: str,
|
|
68
|
+
voice: str,
|
|
69
|
+
tools: list[dict],
|
|
70
|
+
vad: VADSettings | None,
|
|
71
|
+
) -> dict[str, Any]:
|
|
72
|
+
"""Build the `session` object for session.update, GA or preview schema."""
|
|
73
|
+
if self._ga:
|
|
74
|
+
audio_input: dict[str, Any] = {
|
|
75
|
+
"format": {"type": "audio/pcm", "rate": 24000},
|
|
76
|
+
"turn_detection": self._turn_detection(vad),
|
|
77
|
+
}
|
|
78
|
+
if self._transcription_model:
|
|
79
|
+
audio_input["transcription"] = {"model": self._transcription_model}
|
|
80
|
+
session: dict[str, Any] = {
|
|
81
|
+
"type": "realtime",
|
|
82
|
+
"output_modalities": ["audio"],
|
|
83
|
+
"instructions": instructions,
|
|
84
|
+
"audio": {
|
|
85
|
+
"input": audio_input,
|
|
86
|
+
"output": {
|
|
87
|
+
"format": {"type": "audio/pcm", "rate": 24000},
|
|
88
|
+
"voice": voice,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
if tools:
|
|
93
|
+
session["tools"] = tools
|
|
94
|
+
return session
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
"modalities": ["text", "audio"],
|
|
98
|
+
"instructions": instructions,
|
|
99
|
+
"voice": voice,
|
|
100
|
+
"input_audio_format": "pcm16",
|
|
101
|
+
"output_audio_format": "pcm16",
|
|
102
|
+
"input_audio_transcription": {"model": self._transcription_model},
|
|
103
|
+
"tools": tools,
|
|
104
|
+
"turn_detection": self._turn_detection(vad),
|
|
105
|
+
}
|
|
106
|
+
|
|
50
107
|
async def connect(self) -> None:
|
|
51
108
|
self._ws = await websockets.connect(
|
|
52
109
|
self._ws_url, additional_headers=self._ws_headers
|
|
@@ -70,30 +127,10 @@ class OpenAIVoiceProvider:
|
|
|
70
127
|
if not self._ws:
|
|
71
128
|
return
|
|
72
129
|
|
|
73
|
-
session_config
|
|
130
|
+
session_config = {
|
|
74
131
|
"type": "session.update",
|
|
75
|
-
"session":
|
|
76
|
-
"modalities": ["text", "audio"],
|
|
77
|
-
"instructions": instructions,
|
|
78
|
-
"voice": voice,
|
|
79
|
-
"input_audio_format": "pcm16",
|
|
80
|
-
"output_audio_format": "pcm16",
|
|
81
|
-
"input_audio_transcription": {"model": self._transcription_model},
|
|
82
|
-
"tools": tools,
|
|
83
|
-
},
|
|
132
|
+
"session": self._session_body(instructions, voice, tools, vad),
|
|
84
133
|
}
|
|
85
|
-
|
|
86
|
-
if vad is None:
|
|
87
|
-
session_config["session"]["turn_detection"] = None
|
|
88
|
-
else:
|
|
89
|
-
session_config["session"]["turn_detection"] = {
|
|
90
|
-
"type": "server_vad",
|
|
91
|
-
"threshold": vad.threshold,
|
|
92
|
-
"prefix_padding_ms": vad.prefix_padding_ms,
|
|
93
|
-
"silence_duration_ms": vad.silence_duration_ms,
|
|
94
|
-
"create_response": True,
|
|
95
|
-
}
|
|
96
|
-
|
|
97
134
|
await self._ws.send(json.dumps(session_config))
|
|
98
135
|
|
|
99
136
|
async def send_audio(self, base64_chunk: str) -> None:
|
|
@@ -149,10 +186,10 @@ class OpenAIVoiceProvider:
|
|
|
149
186
|
async def update_session(self, instructions: str) -> None:
|
|
150
187
|
if not self._ws:
|
|
151
188
|
return
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
189
|
+
session: dict[str, Any] = {"instructions": instructions}
|
|
190
|
+
if self._ga:
|
|
191
|
+
session["type"] = "realtime"
|
|
192
|
+
msg = {"type": "session.update", "session": session}
|
|
156
193
|
await self._ws.send(json.dumps(msg))
|
|
157
194
|
|
|
158
195
|
async def send_tool_result(self, call_id: str, output: str) -> None:
|
|
@@ -200,14 +237,12 @@ class OpenAIVoiceProvider:
|
|
|
200
237
|
# Configure session with the selected voice
|
|
201
238
|
await ws.send(json.dumps({
|
|
202
239
|
"type": "session.update",
|
|
203
|
-
"session":
|
|
204
|
-
"
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
"turn_detection": None,
|
|
210
|
-
},
|
|
240
|
+
"session": self._session_body(
|
|
241
|
+
instructions="Always speak English. Say a short greeting in 5 words or less.",
|
|
242
|
+
voice=voice,
|
|
243
|
+
tools=[],
|
|
244
|
+
vad=None,
|
|
245
|
+
),
|
|
211
246
|
}))
|
|
212
247
|
|
|
213
248
|
# Send a message and request a response
|
|
@@ -227,7 +262,7 @@ class OpenAIVoiceProvider:
|
|
|
227
262
|
while asyncio.get_event_loop().time() < deadline:
|
|
228
263
|
raw = await asyncio.wait_for(ws.recv(), timeout=15)
|
|
229
264
|
msg = json.loads(raw)
|
|
230
|
-
if msg.get("type")
|
|
265
|
+
if msg.get("type") in ("response.output_audio.delta", "response.audio.delta"):
|
|
231
266
|
audio_chunks.append(base64.b64decode(msg["delta"]))
|
|
232
267
|
elif msg.get("type") == "response.done":
|
|
233
268
|
break
|
|
@@ -245,13 +280,15 @@ class OpenAIVoiceProvider:
|
|
|
245
280
|
def _map_event(self, msg: dict) -> VoiceEvent | None:
|
|
246
281
|
msg_type = msg.get("type", "")
|
|
247
282
|
|
|
248
|
-
|
|
283
|
+
# GA realtime renames the audio/transcript response events
|
|
284
|
+
# (response.output_audio.*); accept both so preview + GA both work.
|
|
285
|
+
if msg_type in ("response.output_audio.delta", "response.audio.delta"):
|
|
249
286
|
return AudioDelta(delta=msg.get("delta", ""))
|
|
250
287
|
|
|
251
|
-
elif msg_type
|
|
288
|
+
elif msg_type in ("response.output_audio_transcript.delta", "response.audio_transcript.delta"):
|
|
252
289
|
return TranscriptDelta(delta=msg.get("delta", ""))
|
|
253
290
|
|
|
254
|
-
elif msg_type
|
|
291
|
+
elif msg_type in ("response.output_audio_transcript.done", "response.audio_transcript.done"):
|
|
255
292
|
return TranscriptDone(transcript=msg.get("transcript", ""))
|
|
256
293
|
|
|
257
294
|
elif msg_type == "conversation.item.input_audio_transcription.completed":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/code_providers/__init__.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/code_providers/claude.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/code_providers/cursor.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/code_providers/openclaw.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/code_providers/registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{voice_vibecoder-2.20.3 → voice_vibecoder-2.22.0}/src/voice_vibecoder/voice_providers/gemini.py
RENAMED
|
File without changes
|
|
File without changes
|