Skip to content

Commit 950b8d6

Browse files
fix: rebuild codebase-review fixes on main without regressions (#127)
Rebuilds the full-codebase-review changes on top of current main. Brings the genuine fixes while preserving everything that landed on main after the review branch was cut: completion-aware call(wait=True)/CallResult, built-in transfer_call/end_call injection in pipeline mode, Plivo carrier, and the modern is_ws_alive websocket-liveness check. Closes the one-sided Python<->TS parity gaps the review left open and corrects defects it introduced. The default Agent.model is aligned across both SDKs to gpt-realtime-mini (matching the TypeScript adapter default and the Python agent() helper); both names are aliases for the same model family and share the same pricing. The realtime-pricing calibration warning now treats both as default-calibrated. Internal review artifacts (docs/reviews/*, docs/DEVLOG.md) were intentionally not carried over. See CHANGELOG.md (## Unreleased) for the user-visible list. Co-authored-by: nicolotognoni <nicolo.tognoni1@gmail.com>
1 parent e80f227 commit 950b8d6

119 files changed

Lines changed: 3094 additions & 1475 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,71 @@
77
live-call, and metrics panels, alongside Twilio and Telnyx
88
(`dashboard-app/`). Also added `"plivo"` to the PyPI / npm package keywords
99
so the SDK surfaces in Plivo-related searches. (#123)
10+
- **`PatterConfigError` (both SDKs).** Added to the TypeScript error taxonomy
11+
(`errors.ts`) to match Python's `PatterConfigError`, and exported from the
12+
Python package root — raised for invalid SDK configuration.
13+
- **New Python package exports**: `resample_pcm`, `define_tool`, `DTMF_EVENTS`,
14+
barge-in strategy helpers, and pricing constants (`PricingUnit`,
15+
`PRICING_VERSION`, `PRICING_LAST_UPDATED`) are now importable from `getpatter`
16+
for parity with the TypeScript surface.
17+
18+
### Changed
19+
20+
- **Public config collections are now immutable.** `Agent.tools` / `guardrails`
21+
/ `text_transforms` / `mcp_servers` and `Guardrail.blocked_terms` are tuples
22+
(Python, `frozen=True`) / `readonly` arrays (TypeScript). Code comparing these
23+
against a literal `list`/array must compare against a tuple / readonly array.
24+
- **Default `Agent.model` is now `gpt-realtime-mini`** (was
25+
`gpt-4o-mini-realtime-preview`), aligning the Python dataclass default with the
26+
TypeScript adapter default. Both are aliases for the same OpenAI Realtime model
27+
family and share the same pricing row, so cost is unaffected — this removes a
28+
Python↔TypeScript default mismatch and an internal Python inconsistency (the
29+
`agent()` helper already defaulted to `gpt-realtime-mini`).
1030

1131
### Fixed
1232

33+
- **DeepFilterNet noise suppression was a silent no-op.** The resampler could
34+
never reach the 48 kHz the model requires, the error was swallowed, and raw
35+
audio passed through unsuppressed. Now resampled correctly
36+
(`providers/deepfilternet_filter.py`, `providers/deepfilternet-filter.ts`).
37+
- **Parallel tool-calls returned a 400 from Anthropic and Gemini.** Multiple
38+
tool results in one turn produced consecutive same-role messages both
39+
providers reject; they are now merged into a single turn in both SDKs
40+
(`providers/anthropic_llm.py` / `anthropic-llm.ts`, `providers/google_llm.py`
41+
/ `google-llm.ts`).
42+
- **Anthropic prompt-cache savings were not billed in TypeScript.** The TS
43+
Anthropic provider now emits cache read/write token counts on its usage chunk
44+
(parity with Python), so cached-prompt cost reductions are reflected in the
45+
metrics (`providers/anthropic-llm.ts`).
46+
- **MCP client URLs were not SSRF-guarded.** Both SDKs now validate the MCP
47+
server URL (blocking link-local, loopback, and private ranges) before opening
48+
the transport (`tools/mcp_client.py`, `tools/mcp-client.ts`).
49+
- **AMD callbacks were clobbered across concurrent outbound calls.** The single
50+
answering-machine-detection slot is now a per-call map in both SDKs, keyed by
51+
call SID (`server.py` / `server.ts`, `client.ts`).
52+
- **`FallbackLLMProvider` crashed in pipeline mode (Python).** `stream()` now
53+
accepts and forwards `cancel_event`, matching how the pipeline invokes every
54+
provider (`services/fallback_provider.py`).
55+
- **Blocking model/inference calls ran on the event loop.** Krisp and Whisper
56+
now offload to `asyncio.to_thread` so audio is not dropped
57+
(`providers/krisp_filter.py`, `providers/whisper_stt.py`).
58+
- **WebSocket reads could hang indefinitely.** Added read timeouts and surfaced
59+
background-task exceptions across providers and the remote-message path
60+
(`services/remote_message.py` / `remote-message.ts`).
61+
- **ElevenLabs API key was a publicly readable field.** It is now private in
62+
both SDKs (`providers/elevenlabs_ws_tts.py`, `providers/elevenlabs-ws-tts.ts`).
63+
- **Async done-callbacks raised `CancelledError` on normal shutdown.** They now
64+
guard `cancelled()` before reading `.exception()` (`observability/event_bus.py`,
65+
`providers/cartesia_stt.py`, `dashboard/store.py`).
66+
- **Pipeline could accumulate orphaned conversation-history turns.** The
67+
speculative user turn is popped on the no-handler / vetoed path
68+
(`stream_handler.py`).
69+
- **Py↔TS parity drift fixed**: `DTMF_EVENTS` ordering, the OpenAI TTS default
70+
model, and the end-of-utterance metric emit guard now match across SDKs.
71+
- **Telnyx STT logs now use the `getpatter.*` namespace.**
72+
`providers/telnyx_stt.py` logged under the stale `patter.providers.telnyx_stt`
73+
namespace; aligned to `getpatter.providers.telnyx_stt` like every other module
74+
so `getpatter.*` log-level filters capture it.
1375
- **Pipeline-mode turns after the first now record per-turn metrics and
1476
broadcast the live dashboard transcript.** `anchorUserSpeechStart()` re-opened
1577
a turn (set `_turnStart`) without clearing the `_turnAlreadyClosed` guard, so

dashboard-app/src/components/CallTable.tsx

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,53 +12,53 @@ import {
1212
} from './icons';
1313

1414
export interface CallCost {
15-
telco?: number;
16-
llm?: number;
17-
stt?: number;
18-
tts?: number;
15+
readonly telco?: number;
16+
readonly llm?: number;
17+
readonly stt?: number;
18+
readonly tts?: number;
1919
/** @deprecated Sum of stt+tts kept for legacy aggregate-spend callers. */
20-
sttTts?: number;
21-
cached?: number;
22-
total?: number;
20+
readonly sttTts?: number;
21+
readonly cached?: number;
22+
readonly total?: number;
2323
}
2424

2525
export type CallMode = 'realtime' | 'pipeline' | 'convai' | 'unknown';
2626

2727
export interface Call {
28-
id: string;
29-
status: 'live' | 'ended' | 'no-answer' | 'queued' | 'fail';
30-
direction: 'inbound' | 'outbound';
31-
from: string;
32-
to: string;
33-
carrier: CallCarrier;
28+
readonly id: string;
29+
readonly status: 'live' | 'ended' | 'no-answer' | 'queued' | 'fail';
30+
readonly direction: 'inbound' | 'outbound';
31+
readonly from: string;
32+
readonly to: string;
33+
readonly carrier: CallCarrier;
3434
/** ms epoch — set for any call we know started, live or ended. */
35-
startedAtMs?: number;
36-
durationStart?: number;
37-
duration?: number;
38-
latencyP95?: number;
39-
latencyP50?: number;
35+
readonly startedAtMs?: number;
36+
readonly durationStart?: number;
37+
readonly duration?: number;
38+
readonly latencyP95?: number;
39+
readonly latencyP50?: number;
4040
/** avg(llm_ms) across this call's turns — for the waterfall llm bar. */
41-
llmAvg?: number;
42-
sttAvg?: number;
43-
ttsAvg?: number;
41+
readonly llmAvg?: number;
42+
readonly sttAvg?: number;
43+
readonly ttsAvg?: number;
4444
/** Number of completed turns. p50/p95 are statistically meaningful only when this is >= 5. */
45-
turnCount?: number;
45+
readonly turnCount?: number;
4646
/** p50 of agent_response_ms (wait time after user stops speaking) — user-perceived latency. */
47-
agentResponseP50?: number;
47+
readonly agentResponseP50?: number;
4848
/** p95 of agent_response_ms — user-perceived latency outlier. */
49-
agentResponseP95?: number;
50-
cost: CallCost;
51-
agent?: string;
52-
model?: string;
53-
mode?: CallMode;
54-
sttProvider?: string;
55-
ttsProvider?: string;
49+
readonly agentResponseP95?: number;
50+
readonly cost: CallCost;
51+
readonly agent?: string;
52+
readonly model?: string;
53+
readonly mode?: CallMode;
54+
readonly sttProvider?: string;
55+
readonly ttsProvider?: string;
5656
/** Model identifier within the provider, e.g. "ink-whisper", "eleven_flash_v2_5", "gpt-oss-120b". */
57-
sttModel?: string;
58-
ttsModel?: string;
59-
llmModel?: string;
60-
transcriptKey?: string;
61-
endedAgo?: number;
57+
readonly sttModel?: string;
58+
readonly ttsModel?: string;
59+
readonly llmModel?: string;
60+
readonly transcriptKey?: string;
61+
readonly endedAgo?: number;
6262
}
6363

6464
interface CallRowProps {

dashboard-app/src/components/CostPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ function titleCase(s: string): string {
1818
}
1919

2020
export function CostPanel({ call }: CostPanelProps) {
21-
if (!call || !call.cost?.telco) return null;
21+
if (!call || call.cost?.telco == null) return null;
2222

2323
const c = call.cost;
2424
const telco = c.telco ?? 0;

dashboard-app/src/components/LatencyPanel.tsx

Lines changed: 0 additions & 112 deletions
This file was deleted.

dashboard-app/src/components/LiveCallPanel.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import type { Call } from './CallTable';
33
import { fmtDuration, fmtPhone } from './format';
44
import { IconForward, IconHangup, IconMic, IconRecord } from './icons';
55
import { CARRIERS } from '../lib/mappers';
6+
import type { TranscriptTurn } from '../lib/mappers';
7+
8+
export type { TranscriptTurn };
69

710
interface LiveDurationProps {
811
start: number;
@@ -17,14 +20,6 @@ function LiveDuration({ start }: LiveDurationProps) {
1720
return <>{fmtDuration((Date.now() - start) / 1000)}</>;
1821
}
1922

20-
export interface TranscriptTurn {
21-
who: 'user' | 'bot' | 'tool';
22-
txt?: string;
23-
args?: Record<string, string | number>;
24-
typing?: boolean;
25-
lat?: { stt?: number; llm?: number; tts?: number; total?: number };
26-
}
27-
2823
export interface LiveCallPanelProps {
2924
call: Call | null;
3025
transcript: TranscriptTurn[];

dashboard-app/src/components/MetricsPanel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,14 @@ function LatencyView({ call }: { call: Call }) {
8383
<div className="latbox">
8484
<div className="l">end-to-end p50</div>
8585
<div className="v">
86-
{showPctRt ? p50 || '—' : '—'}
86+
{showPctRt ? p50 ?? '—' : '—'}
8787
{showPctRt && <span className="u">ms</span>}
8888
</div>
8989
</div>
9090
<div className={'latbox' + (showPctRt && p95 > 600 ? ' warn' : '')}>
9191
<div className="l">end-to-end p95</div>
9292
<div className="v">
93-
{showPctRt ? p95 || '—' : '—'}
93+
{showPctRt ? p95 ?? '—' : '—'}
9494
{showPctRt && <span className="u">ms</span>}
9595
</div>
9696
</div>

0 commit comments

Comments
 (0)