@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/README.md +3 -3
- package/bin/example-runner.ts +4 -0
- package/dist/bin/example-runner.js +4 -0
- package/dist/bin/example-runner.js.map +1 -1
- package/dist/examples/warm-transfer.d.ts +1 -0
- package/dist/examples/warm-transfer.js +111 -0
- package/dist/examples/warm-transfer.js.map +1 -0
- package/dist/src/agent.d.ts +5 -2
- package/dist/src/agent.js +114 -108
- package/dist/src/agent.js.map +1 -1
- package/dist/src/call.d.ts +82 -37
- package/dist/src/call.js +227 -91
- package/dist/src/call.js.map +1 -1
- package/dist/src/commands.d.ts +36 -0
- package/dist/src/commands.js +17 -1
- package/dist/src/commands.js.map +1 -1
- package/dist/src/events.d.ts +12 -0
- package/dist/src/events.js +7 -1
- package/dist/src/events.js.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +3 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/runner.js +12 -2
- package/dist/src/runner.js.map +1 -1
- package/dist/src/utils.d.ts +12 -0
- package/dist/src/utils.js +43 -1
- package/dist/src/utils.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/examples/README.md +1 -0
- package/examples/warm-transfer.ts +90 -0
- package/package.json +1 -1
- package/src/agent.ts +105 -45
- package/src/call.ts +207 -111
- package/src/commands.ts +22 -0
- package/src/events.ts +8 -0
- package/src/index.ts +2 -1
- package/src/runner.ts +11 -2
- package/src/utils.ts +48 -1
- package/src/version.ts +1 -1
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1
|
+
export const SDK_VERSION = "0.44.0";
|