@guava-ai/guava-sdk 0.43.0 → 0.44.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/src/commands.ts CHANGED
@@ -111,6 +111,12 @@ export const TransferCommand = z.strictObject({
111
111
  });
112
112
  export type TransferCommand = z.input<typeof TransferCommand>;
113
113
 
114
+ export const HangupCommand = z.strictObject({
115
+ command_type: z.literal("hangup-call"),
116
+ final_script: z.string().nullish().default(null),
117
+ });
118
+ export type HangupCommand = z.input<typeof HangupCommand>;
119
+
114
120
  export const RegisteredHooksCommand = z.strictObject({
115
121
  command_type: z.literal("registered-hooks"),
116
122
  has_on_question: z.boolean(),
@@ -150,6 +156,13 @@ export const RetryTaskCommand = z.strictObject({
150
156
  });
151
157
  export type RetryTaskCommand = z.input<typeof RetryTaskCommand>;
152
158
 
159
+ export const SetVariableCommand = z.strictObject({
160
+ command_type: z.literal("set-variable"),
161
+ key: z.string(),
162
+ value: z.json(),
163
+ });
164
+ export type SetVariableCommand = z.input<typeof SetVariableCommand>;
165
+
153
166
  export const SetAgentDTMFCommand = z.strictObject({
154
167
  command_type: z.literal("set-agent-dtmf"),
155
168
  enabled: z.boolean(),
@@ -162,6 +175,12 @@ export const SendAgentDTMFCommand = z.strictObject({
162
175
  });
163
176
  export type SendAgentDTMFCommand = z.input<typeof SendAgentDTMFCommand>;
164
177
 
178
+ export const SendWebRTCMessageCommand = z.strictObject({
179
+ command_type: z.literal("send-webrtc-message"),
180
+ payload: z.record(z.string(), z.json()).default({}),
181
+ });
182
+ export type SendWebRTCMessageCommand = z.input<typeof SendWebRTCMessageCommand>;
183
+
165
184
  export const ExpertErrorCommand = z.strictObject({
166
185
  command_type: z.literal("expert-error"),
167
186
  message: z.string(),
@@ -180,12 +199,15 @@ export const AnyCommand = z.union([
180
199
  SetLanguageModeCommand,
181
200
  SendInstructionCommand,
182
201
  TransferCommand,
202
+ HangupCommand,
183
203
  RegisteredHooksCommand,
184
204
  ChoiceResultCommand,
185
205
  ActionSuggestionCommand,
186
206
  RetryTaskCommand,
207
+ SetVariableCommand,
187
208
  SetAgentDTMFCommand,
188
209
  SendAgentDTMFCommand,
210
+ SendWebRTCMessageCommand,
189
211
  ExpertErrorCommand,
190
212
  ]);
191
213
  export type Command = z.input<typeof AnyCommand>;
package/src/events.ts CHANGED
@@ -165,6 +165,13 @@ export const AgentDTMFSentEvent = z.object({
165
165
  });
166
166
  export type AgentDTMFSentEvent = z.infer<typeof AgentDTMFSentEvent>;
167
167
 
168
+ /** A message sent by a connected WebRTC client. */
169
+ export const WebRTCMessageEvent = z.object({
170
+ event_type: z.literal("webrtc-message"),
171
+ payload: z.record(z.string(), z.json()).default({}),
172
+ });
173
+ export type WebRTCMessageEvent = z.infer<typeof WebRTCMessageEvent>;
174
+
168
175
  export const EscalateEvent = z.object({
169
176
  event_type: z.literal("escalate"),
170
177
  requested_by: z.enum(["human", "agent"]).default("human"),
@@ -191,6 +198,7 @@ export const GuavaEvent = z.discriminatedUnion("event_type", [
191
198
  EscalateEvent,
192
199
  DTMFPressedEvent,
193
200
  AgentDTMFSentEvent,
201
+ WebRTCMessageEvent,
194
202
  ]);
195
203
  export type GuavaEvent = z.infer<typeof GuavaEvent>;
196
204
 
package/src/index.ts CHANGED
@@ -14,7 +14,7 @@ export { Logger, getConsoleLogger, getDefaultLogger } from "./logging.ts";
14
14
  export { Agent, CallInfo } from "./agent.ts";
15
15
  export { SIPCallInfo } from "./socket/call-info.ts";
16
16
  export { SipUri } from "./sip.ts";
17
- export { Call, DEFAULT_REACH_PERSON_OUTCOMES } from "./call.ts";
17
+ export { BaseCall, Call, RemoteCall, DEFAULT_REACH_PERSON_OUTCOMES } from "./call.ts";
18
18
  export type { ReachPersonOutcome } from "./call.ts";
19
19
  export type { SpeedPreset, Language } from "./commands.ts";
20
20
  export type {
@@ -24,6 +24,7 @@ export type {
24
24
  AgentDTMFSentEvent,
25
25
  DTMFDigit,
26
26
  OutboundCallFailed,
27
+ WebRTCMessageEvent,
27
28
  } from "./events.ts";
28
29
  export { TestSession } from "./testing/session.ts";
29
30
  export { MockCall } from "./testing/mocks.ts";
package/src/runner.ts CHANGED
@@ -28,7 +28,9 @@ export class Runner {
28
28
  listenWebrtc(agent: Agent, webrtcCode?: string): this {
29
29
  const ctx = this._healthCtx.createCtx();
30
30
  this._tasks.push(async () => {
31
- const code = webrtcCode ?? (await agent._client.createWebrtcAgent(3600));
31
+ // Unlike the Python SDK, code creation happens here to prevent Runner.listen_webrtc from becoming async.
32
+ if (!webrtcCode) this._logger.info("No WebRTC code provided. Creating a temporary one.");
33
+ const code = webrtcCode || (await agent._client.createWebrtcAgent(3600));
32
34
  return agent._listenInbound(ctx, { webrtc_code: code }, {}, this._drainController.signal);
33
35
  });
34
36
  return this;
@@ -56,7 +58,14 @@ export class Runner {
56
58
  this._drainController.abort();
57
59
  })
58
60
  : nullDisposable;
59
- await Promise.all(this._tasks.map((task) => task()));
61
+ // Wait for every task, so one failing doesn't tear down signal handling and
62
+ // the health server while others are still draining.
63
+ const results = await Promise.allSettled(this._tasks.map((task) => task()));
60
64
  this._logger.info("All tasks done.");
65
+
66
+ const errors = results.flatMap((r) => (r.status === "rejected" ? [r.reason] : []));
67
+ if (errors.length === 1) throw errors[0];
68
+ if (errors.length > 1)
69
+ throw new AggregateError(errors, `${errors.length} runner tasks failed.`);
61
70
  }
62
71
  }
package/src/utils.ts CHANGED
@@ -64,11 +64,58 @@ export async function fetchOrThrow(
64
64
  return res;
65
65
  }
66
66
 
67
+ /**
68
+ * Whether `e` is a fetch network failure. Bad input (e.g. an invalid URL) is
69
+ * also a TypeError, but with a different message. Avoids `instanceof`, since
70
+ * fetch errors can come from another realm (e.g. under Jest).
71
+ */
72
+ function isNetworkError(e: unknown): boolean {
73
+ const { name, message } = (e ?? {}) as { name?: unknown; message?: unknown };
74
+ return name === "TypeError" && message === "fetch failed";
75
+ }
76
+
77
+ const RETRYABLE_STATUS_CODES = new Set([408, 409, 429, 500, 502, 503, 504]);
78
+ const RETRY_DELAYS_MS = [0, 500, 1000, 2000, 5000];
79
+
80
+ /**
81
+ * POST that retries network errors and retryable statuses, throwing on a final
82
+ * error status. A single Idempotency-Key is sent with every attempt so the
83
+ * server can drop duplicates.
84
+ *
85
+ * @param retries - Number of retries after the first attempt.
86
+ */
87
+ export async function idempotentPost(
88
+ input: string | URL,
89
+ init: Omit<RequestInit, "method"> = {},
90
+ retries = 3,
91
+ ): Promise<Response> {
92
+ const headers = new Headers(init.headers);
93
+ if (!headers.has("Idempotency-Key")) headers.set("Idempotency-Key", crypto.randomUUID());
94
+
95
+ for (let attempt = 0; ; attempt++) {
96
+ try {
97
+ return await fetchOrThrow(input, { ...init, method: "POST", headers });
98
+ } catch (e) {
99
+ const retryable =
100
+ isNetworkError(e) || (e instanceof HttpStatusError && RETRYABLE_STATUS_CODES.has(e.status));
101
+ if (attempt === retries || !retryable) throw e;
102
+ }
103
+ await sleep(RETRY_DELAYS_MS[Math.min(attempt, RETRY_DELAYS_MS.length - 1)]!);
104
+ }
105
+ }
106
+
67
107
  /** A Disposable whose disposal does nothing. Useful as a conditional no-op. */
68
108
  export const nullDisposable: Disposable = { [Symbol.dispose]() {} };
69
109
 
110
+ /**
111
+ * Run `listener` when `signal` aborts, or immediately if it already has.
112
+ * Dispose to unregister.
113
+ */
70
114
  export function onAbort(signal: AbortSignal, listener: () => void): Disposable {
71
- signal.throwIfAborted();
115
+ if (signal.aborted) {
116
+ listener();
117
+ return nullDisposable;
118
+ }
72
119
  signal.addEventListener("abort", listener, { once: true });
73
120
 
74
121
  return {
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = "0.43.0";
1
+ export const SDK_VERSION = "0.44.0";