getpatter 0.4.3 → 0.5.0

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.
package/dist/cli.js CHANGED
@@ -33,9 +33,15 @@ var MetricsStore = class extends import_events.EventEmitter {
33
33
  maxCalls;
34
34
  calls = [];
35
35
  activeCalls = /* @__PURE__ */ new Map();
36
- constructor(maxCalls = 500) {
36
+ /**
37
+ * Accepts either a numeric ``maxCalls`` (legacy positional — matches the
38
+ * original TS API) or an options object ``{ maxCalls }`` to align with the
39
+ * Python SDK's keyword-argument style. Plain literals also work:
40
+ * ``new MetricsStore()`` / ``new MetricsStore(100)`` / ``new MetricsStore({ maxCalls: 100 })``.
41
+ */
42
+ constructor(maxCallsOrOpts = 500) {
37
43
  super();
38
- this.maxCalls = maxCalls;
44
+ this.maxCalls = typeof maxCallsOrOpts === "number" ? maxCallsOrOpts : maxCallsOrOpts.maxCalls ?? 500;
39
45
  }
40
46
  publish(eventType, data) {
41
47
  this.emit("sse", { type: eventType, data });
@@ -43,22 +49,100 @@ var MetricsStore = class extends import_events.EventEmitter {
43
49
  recordCallStart(data) {
44
50
  const callId = data.call_id || "";
45
51
  if (!callId) return;
52
+ const existing = this.activeCalls.get(callId);
53
+ if (existing) {
54
+ existing.caller = data.caller || existing.caller;
55
+ existing.callee = data.callee || existing.callee;
56
+ existing.direction = data.direction || existing.direction;
57
+ existing.status = "in-progress";
58
+ existing.turns = existing.turns || [];
59
+ } else {
60
+ const record = {
61
+ call_id: callId,
62
+ caller: data.caller || "",
63
+ callee: data.callee || "",
64
+ direction: data.direction || "inbound",
65
+ started_at: Date.now() / 1e3,
66
+ status: "in-progress",
67
+ turns: []
68
+ };
69
+ this.activeCalls.set(callId, record);
70
+ }
71
+ this.publish("call_start", {
72
+ call_id: callId,
73
+ caller: data.caller || "",
74
+ callee: data.callee || "",
75
+ direction: data.direction || "inbound"
76
+ });
77
+ }
78
+ /**
79
+ * Pre-register an outbound call before any webhook fires. Lets the
80
+ * dashboard surface attempts that never reach media (no-answer, busy,
81
+ * carrier-rejected). Mirrors the Python ``record_call_initiated``.
82
+ */
83
+ recordCallInitiated(data) {
84
+ const callId = data.call_id || "";
85
+ if (!callId) return;
86
+ if (this.activeCalls.has(callId)) return;
46
87
  const record = {
47
88
  call_id: callId,
48
89
  caller: data.caller || "",
49
90
  callee: data.callee || "",
50
- direction: data.direction || "inbound",
91
+ direction: data.direction || "outbound",
51
92
  started_at: Date.now() / 1e3,
93
+ status: "initiated",
52
94
  turns: []
53
95
  };
54
96
  this.activeCalls.set(callId, record);
55
- this.publish("call_start", {
97
+ this.publish("call_initiated", {
56
98
  call_id: callId,
57
99
  caller: record.caller,
58
100
  callee: record.callee,
59
- direction: record.direction
101
+ direction: record.direction,
102
+ status: record.status
60
103
  });
61
104
  }
105
+ /**
106
+ * Update the status of an active or completed call. Terminal states
107
+ * (completed, no-answer, busy, failed, canceled, webhook_error) move the
108
+ * row from active to completed so the UI freezes the live duration timer.
109
+ */
110
+ updateCallStatus(callId, status, extra = {}) {
111
+ if (!callId || !status) return;
112
+ const TERMINAL = /* @__PURE__ */ new Set(["completed", "no-answer", "busy", "failed", "canceled", "webhook_error"]);
113
+ const active = this.activeCalls.get(callId);
114
+ if (active) {
115
+ active.status = status;
116
+ Object.assign(active, extra);
117
+ if (TERMINAL.has(status)) {
118
+ const entry = {
119
+ call_id: callId,
120
+ caller: active.caller || "",
121
+ callee: active.callee || "",
122
+ direction: active.direction || "outbound",
123
+ started_at: active.started_at || 0,
124
+ ended_at: Date.now() / 1e3,
125
+ status,
126
+ metrics: null,
127
+ ...extra
128
+ };
129
+ this.activeCalls.delete(callId);
130
+ this.calls.push(entry);
131
+ if (this.calls.length > this.maxCalls) {
132
+ this.calls = this.calls.slice(-this.maxCalls);
133
+ }
134
+ }
135
+ } else {
136
+ for (let i = this.calls.length - 1; i >= 0; i--) {
137
+ if (this.calls[i].call_id === callId) {
138
+ this.calls[i].status = status;
139
+ Object.assign(this.calls[i], extra);
140
+ break;
141
+ }
142
+ }
143
+ }
144
+ this.publish("call_status", { call_id: callId, status, ...extra });
145
+ }
62
146
  recordTurn(data) {
63
147
  const callId = data.call_id || "";
64
148
  const turn = data.turn;
@@ -75,6 +159,8 @@ var MetricsStore = class extends import_events.EventEmitter {
75
159
  if (!callId) return;
76
160
  const active = this.activeCalls.get(callId);
77
161
  this.activeCalls.delete(callId);
162
+ const activeStatus = active?.status;
163
+ const resolvedStatus = activeStatus && activeStatus !== "in-progress" ? activeStatus : "completed";
78
164
  const entry = {
79
165
  call_id: callId,
80
166
  caller: data.caller || active?.caller || "",
@@ -83,6 +169,7 @@ var MetricsStore = class extends import_events.EventEmitter {
83
169
  started_at: active?.started_at || 0,
84
170
  ended_at: Date.now() / 1e3,
85
171
  transcript: data.transcript || [],
172
+ status: resolvedStatus,
86
173
  metrics: metrics ?? null
87
174
  };
88
175
  this.calls.push(entry);