@cosmoops/connect-sdk 0.27.0 → 0.29.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/CHANGELOG.md +93 -0
- package/dist/cjs/server/client.js +5 -1
- package/dist/cjs/server/resources/conversations.js +70 -0
- package/dist/esm/server/client.js +5 -1
- package/dist/esm/server/resources/conversations.js +66 -0
- package/dist/types/server/client.d.ts +4 -1
- package/dist/types/server/resources/conversations.d.ts +68 -0
- package/dist/types/types.d.ts +268 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,12 +3,105 @@
|
|
|
3
3
|
`@cosmoops/connect-sdk` follows semver, with the 0.x convention that **minor
|
|
4
4
|
versions may carry breaking changes** and patch versions never do.
|
|
5
5
|
|
|
6
|
+
## 0.29.0
|
|
7
|
+
|
|
8
|
+
Voice was readable and writing was not. `calls.list` has always let a partner
|
|
9
|
+
read back what a customer said out loud; the WhatsApp threads a workspace's
|
|
10
|
+
agents hold had no partner surface at all, so a CRM could show every call on a
|
|
11
|
+
contact and nothing they had typed.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `connect.conversations.list({ workspaceId, contactAddress?, status? })` and
|
|
16
|
+
`connect.conversations.get(id, { workspaceId })`, plus the four types they
|
|
17
|
+
answer with: `ConnectConversation`, `ConnectConversationMessage`,
|
|
18
|
+
`ConversationsPage` and `ConversationDetail`.
|
|
19
|
+
|
|
20
|
+
**These are the *workspace's* threads, not your application's**, which is the
|
|
21
|
+
difference from `calls` and it decides how they are gated. A call arrived on
|
|
22
|
+
a channel you hold, so `calls.list` is scoped to your application by the key
|
|
23
|
+
alone. A conversation lives in the workspace's own subtree and belongs to it
|
|
24
|
+
the way its contact book does — so reading one needs the `conversations:read`
|
|
25
|
+
scope **and** a live subscription in that workspace, and, when the call comes
|
|
26
|
+
through the browser bridge, the signed-in member's own `agent:read`.
|
|
27
|
+
|
|
28
|
+
Three things are worth reading before you render one:
|
|
29
|
+
|
|
30
|
+
`contactAddress` is the filter to reach for beside your own customer record.
|
|
31
|
+
You hold a phone number, and you cannot derive a conversation id from one —
|
|
32
|
+
the id is built from a hashed key, deliberately, so an id you could guess is
|
|
33
|
+
an id you could probe. Send the number however your records spell it.
|
|
34
|
+
|
|
35
|
+
`canReplyFreely` is computed against Connect's clock, and re-deriving it from
|
|
36
|
+
`lastInboundAt` against yours is the mistake to avoid: WhatsApp allows a
|
|
37
|
+
free-form reply only inside 24 hours of the customer's last message, and a
|
|
38
|
+
skew between your server and Connect's shows up as a thread your UI calls
|
|
39
|
+
answerable when it is not.
|
|
40
|
+
|
|
41
|
+
Messages that sent nothing are included and carry `failure`. Render them — a
|
|
42
|
+
transcript that silently drops them reads as a customer being ignored, which
|
|
43
|
+
is the opposite of what happened. `refused` is not a failure at all: it is
|
|
44
|
+
the agent deciding not to answer, which is what it is for.
|
|
45
|
+
|
|
46
|
+
Read-only, and deliberately. Sending on a thread would mean saying whether it
|
|
47
|
+
is a free-form reply or an approved template, and what that means for the
|
|
48
|
+
agent still answering it.
|
|
49
|
+
|
|
50
|
+
## 0.28.0
|
|
51
|
+
|
|
52
|
+
Connect's webhook bus carried four events, all of them about billing and
|
|
53
|
+
deletion. It now carries the seven a system of record is actually built on —
|
|
54
|
+
calls, appointments and contacts — so the work your customers do inside
|
|
55
|
+
Connect can reach the product they do the rest of their work in.
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- Seven webhook event types, with their payloads fully typed on
|
|
60
|
+
`ConnectWebhookEventMap`: `call.completed`, `call.missed`,
|
|
61
|
+
`booking.confirmed`, `booking.cancelled`, `booking.rescheduled`,
|
|
62
|
+
`contact.created` and `contact.updated`. Register handlers for them on
|
|
63
|
+
`createConnectWebhookHandler` exactly as you do the existing four.
|
|
64
|
+
- `ConnectCallDirection` and `ConnectContactSource`, the two unions those
|
|
65
|
+
payloads carry.
|
|
66
|
+
|
|
67
|
+
These are **activity** events rather than lifecycle ones, and the difference
|
|
68
|
+
decides who receives them: they are delivered only to applications the
|
|
69
|
+
workspace is actually subscribed to, so a lapsed customer stops emitting on
|
|
70
|
+
the same boundary that stops their access.
|
|
71
|
+
|
|
72
|
+
Three conventions are worth reading before you write a handler, each
|
|
73
|
+
documented on the types themselves. Phone numbers are E.164. A call event
|
|
74
|
+
identifies its party by `contactPhone` and deliberately carries no
|
|
75
|
+
`contactId` — Connect resolves a caller *by* the number and at the moment a
|
|
76
|
+
call ends it often has not, so the field would be `null` on most deliveries.
|
|
77
|
+
And `booking.rescheduled` carries `previousBookingId` as well as `bookingId`,
|
|
78
|
+
because Connect retires the old booking and writes a new one: reconcile on
|
|
79
|
+
the previous id or you will end up holding two appointments.
|
|
80
|
+
|
|
81
|
+
### Fixed
|
|
82
|
+
|
|
83
|
+
- `Booking` fixtures in the bundled tests compile again — the type gained a
|
|
84
|
+
required `hostMemberIds` in 0.27.0 and one fixture was missed. No runtime
|
|
85
|
+
change.
|
|
86
|
+
|
|
87
|
+
### A note on exhaustiveness
|
|
88
|
+
|
|
89
|
+
`ConnectWebhookEventType` is a union that grows, and this release grows it.
|
|
90
|
+
`createConnectWebhookHandler` is unaffected — its `on` map is partial and it
|
|
91
|
+
answers 200 to anything you have not registered. A hand-written `switch` with
|
|
92
|
+
an exhaustive `never` check on that union will now fail to compile until you
|
|
93
|
+
add the new members; that is the documented contract of this union rather than
|
|
94
|
+
a break, and it is the reason the handler exists.
|
|
95
|
+
|
|
6
96
|
## 0.27.0
|
|
7
97
|
|
|
8
98
|
A minor rather than a patch because `Booking` gains a required field, so a
|
|
9
99
|
hand-written object satisfying the old interface no longer type-checks. Nothing
|
|
10
100
|
you *read* from the API breaks.
|
|
11
101
|
|
|
102
|
+
0.26.1 was never published; its note below ships as part of this release, so the
|
|
103
|
+
registry goes 0.26.0 → 0.27.0.
|
|
104
|
+
|
|
12
105
|
### Added
|
|
13
106
|
|
|
14
107
|
- **`connect.scheduling.getBooking()`** - one appointment, by the id you
|
|
@@ -11,6 +11,7 @@ const scheduling_js_1 = require("./resources/scheduling.js");
|
|
|
11
11
|
const contacts_js_1 = require("./resources/contacts.js");
|
|
12
12
|
const channels_js_1 = require("./resources/channels.js");
|
|
13
13
|
const calls_js_1 = require("./resources/calls.js");
|
|
14
|
+
const conversations_js_1 = require("./resources/conversations.js");
|
|
14
15
|
const kv_js_1 = require("./resources/kv.js");
|
|
15
16
|
const billing_js_1 = require("./resources/billing.js");
|
|
16
17
|
const drive_js_1 = require("./resources/drive.js");
|
|
@@ -92,6 +93,8 @@ class ConnectClient {
|
|
|
92
93
|
channels;
|
|
93
94
|
/** The calls that arrived on those channels, with their transcripts. */
|
|
94
95
|
calls;
|
|
96
|
+
/** The message threads a subscribing workspace's agents are holding. */
|
|
97
|
+
conversations;
|
|
95
98
|
/** RAG chat and structured extraction. */
|
|
96
99
|
ai;
|
|
97
100
|
enrichment;
|
|
@@ -145,6 +148,7 @@ class ConnectClient {
|
|
|
145
148
|
this.contacts = new contacts_js_1.ContactsResource(transport);
|
|
146
149
|
this.channels = new channels_js_1.ChannelsResource(transport);
|
|
147
150
|
this.calls = new calls_js_1.CallsResource(transport);
|
|
151
|
+
this.conversations = new conversations_js_1.ConversationsResource(transport);
|
|
148
152
|
this.ai = new ai_js_1.AiResource(transport);
|
|
149
153
|
this.enrichment = new enrichment_js_1.EnrichmentResource(transport);
|
|
150
154
|
this.data = new data_js_1.DataResource(transport);
|
|
@@ -161,4 +165,4 @@ class ConnectClient {
|
|
|
161
165
|
}
|
|
162
166
|
exports.ConnectClient = ConnectClient;
|
|
163
167
|
/** Kept in step with `package.json` by `sdk.test.ts`. */
|
|
164
|
-
exports.SDK_VERSION = "0.
|
|
168
|
+
exports.SDK_VERSION = "0.29.0";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConversationsResource = void 0;
|
|
4
|
+
const http_js_1 = require("../http.js");
|
|
5
|
+
const acting_as_js_1 = require("./acting-as.js");
|
|
6
|
+
/**
|
|
7
|
+
* What a workspace's agents are saying in writing.
|
|
8
|
+
*
|
|
9
|
+
* **The sibling of `calls`, with one difference that decides how it is
|
|
10
|
+
* gated: these are the *workspace's* threads, not your application's.** A call
|
|
11
|
+
* arrived on a channel you hold, so `calls.list` is scoped to your
|
|
12
|
+
* application. A conversation lives in the workspace's own subtree and belongs
|
|
13
|
+
* to it the way its contact book does - so reading one needs a live
|
|
14
|
+
* subscription in that workspace, and, when the call comes from a browser
|
|
15
|
+
* through the bridge, the signed-in member's own `agent:read`.
|
|
16
|
+
*
|
|
17
|
+
* Read-only, and deliberately. Sending on a thread is not a missing verb:
|
|
18
|
+
* inside WhatsApp's 24-hour window a reply is the agent's to make, outside it
|
|
19
|
+
* only an approved template may be sent, and posting into a thread would mean
|
|
20
|
+
* saying which of those it was and what it means for the agent still
|
|
21
|
+
* answering.
|
|
22
|
+
*/
|
|
23
|
+
class ConversationsResource {
|
|
24
|
+
transport;
|
|
25
|
+
constructor(transport) {
|
|
26
|
+
this.transport = transport;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The workspace's threads, newest activity first.
|
|
30
|
+
*
|
|
31
|
+
* **Page until `hasMore` is `false`; do not stop on an empty `items`.** An
|
|
32
|
+
* inbox grows without bound, so there is no total count to page against.
|
|
33
|
+
*/
|
|
34
|
+
list(input) {
|
|
35
|
+
return (0, http_js_1.request)(this.transport, {
|
|
36
|
+
method: "GET",
|
|
37
|
+
path: "/conversations",
|
|
38
|
+
query: {
|
|
39
|
+
workspaceId: input.workspaceId,
|
|
40
|
+
contactAddress: input.contactAddress,
|
|
41
|
+
status: input.status,
|
|
42
|
+
cursor: input.cursor,
|
|
43
|
+
limit: input.limit === undefined ? undefined : String(input.limit),
|
|
44
|
+
},
|
|
45
|
+
headers: (0, acting_as_js_1.actingAs)(input.asUser),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* One thread and its recent messages, oldest first.
|
|
50
|
+
*
|
|
51
|
+
* **Capped, not paged** - you get the same window the agent itself sees when
|
|
52
|
+
* it composes a reply, so you are never shown less context than it used.
|
|
53
|
+
*
|
|
54
|
+
* Turns that produced no message are included and carry `failure`. Render
|
|
55
|
+
* them: a transcript that silently drops them reads as a customer being
|
|
56
|
+
* ignored, which is the opposite of what happened.
|
|
57
|
+
*
|
|
58
|
+
* Throws `ConnectError` with `code: "not_found"` for a thread this workspace
|
|
59
|
+
* does not hold.
|
|
60
|
+
*/
|
|
61
|
+
get(conversationId, options) {
|
|
62
|
+
return (0, http_js_1.request)(this.transport, {
|
|
63
|
+
method: "GET",
|
|
64
|
+
path: `/conversations/${encodeURIComponent(conversationId)}`,
|
|
65
|
+
query: { workspaceId: options.workspaceId },
|
|
66
|
+
headers: (0, acting_as_js_1.actingAs)(options.asUser),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.ConversationsResource = ConversationsResource;
|
|
@@ -8,6 +8,7 @@ import { SchedulingResource } from "./resources/scheduling.js";
|
|
|
8
8
|
import { ContactsResource } from "./resources/contacts.js";
|
|
9
9
|
import { ChannelsResource } from "./resources/channels.js";
|
|
10
10
|
import { CallsResource } from "./resources/calls.js";
|
|
11
|
+
import { ConversationsResource } from "./resources/conversations.js";
|
|
11
12
|
import { KvResource } from "./resources/kv.js";
|
|
12
13
|
import { BillingResource } from "./resources/billing.js";
|
|
13
14
|
import { DriveResource } from "./resources/drive.js";
|
|
@@ -89,6 +90,8 @@ export class ConnectClient {
|
|
|
89
90
|
channels;
|
|
90
91
|
/** The calls that arrived on those channels, with their transcripts. */
|
|
91
92
|
calls;
|
|
93
|
+
/** The message threads a subscribing workspace's agents are holding. */
|
|
94
|
+
conversations;
|
|
92
95
|
/** RAG chat and structured extraction. */
|
|
93
96
|
ai;
|
|
94
97
|
enrichment;
|
|
@@ -142,6 +145,7 @@ export class ConnectClient {
|
|
|
142
145
|
this.contacts = new ContactsResource(transport);
|
|
143
146
|
this.channels = new ChannelsResource(transport);
|
|
144
147
|
this.calls = new CallsResource(transport);
|
|
148
|
+
this.conversations = new ConversationsResource(transport);
|
|
145
149
|
this.ai = new AiResource(transport);
|
|
146
150
|
this.enrichment = new EnrichmentResource(transport);
|
|
147
151
|
this.data = new DataResource(transport);
|
|
@@ -157,4 +161,4 @@ export class ConnectClient {
|
|
|
157
161
|
}
|
|
158
162
|
}
|
|
159
163
|
/** Kept in step with `package.json` by `sdk.test.ts`. */
|
|
160
|
-
export const SDK_VERSION = "0.
|
|
164
|
+
export const SDK_VERSION = "0.29.0";
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { request } from "../http.js";
|
|
2
|
+
import { actingAs } from "./acting-as.js";
|
|
3
|
+
/**
|
|
4
|
+
* What a workspace's agents are saying in writing.
|
|
5
|
+
*
|
|
6
|
+
* **The sibling of `calls`, with one difference that decides how it is
|
|
7
|
+
* gated: these are the *workspace's* threads, not your application's.** A call
|
|
8
|
+
* arrived on a channel you hold, so `calls.list` is scoped to your
|
|
9
|
+
* application. A conversation lives in the workspace's own subtree and belongs
|
|
10
|
+
* to it the way its contact book does - so reading one needs a live
|
|
11
|
+
* subscription in that workspace, and, when the call comes from a browser
|
|
12
|
+
* through the bridge, the signed-in member's own `agent:read`.
|
|
13
|
+
*
|
|
14
|
+
* Read-only, and deliberately. Sending on a thread is not a missing verb:
|
|
15
|
+
* inside WhatsApp's 24-hour window a reply is the agent's to make, outside it
|
|
16
|
+
* only an approved template may be sent, and posting into a thread would mean
|
|
17
|
+
* saying which of those it was and what it means for the agent still
|
|
18
|
+
* answering.
|
|
19
|
+
*/
|
|
20
|
+
export class ConversationsResource {
|
|
21
|
+
transport;
|
|
22
|
+
constructor(transport) {
|
|
23
|
+
this.transport = transport;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The workspace's threads, newest activity first.
|
|
27
|
+
*
|
|
28
|
+
* **Page until `hasMore` is `false`; do not stop on an empty `items`.** An
|
|
29
|
+
* inbox grows without bound, so there is no total count to page against.
|
|
30
|
+
*/
|
|
31
|
+
list(input) {
|
|
32
|
+
return request(this.transport, {
|
|
33
|
+
method: "GET",
|
|
34
|
+
path: "/conversations",
|
|
35
|
+
query: {
|
|
36
|
+
workspaceId: input.workspaceId,
|
|
37
|
+
contactAddress: input.contactAddress,
|
|
38
|
+
status: input.status,
|
|
39
|
+
cursor: input.cursor,
|
|
40
|
+
limit: input.limit === undefined ? undefined : String(input.limit),
|
|
41
|
+
},
|
|
42
|
+
headers: actingAs(input.asUser),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* One thread and its recent messages, oldest first.
|
|
47
|
+
*
|
|
48
|
+
* **Capped, not paged** - you get the same window the agent itself sees when
|
|
49
|
+
* it composes a reply, so you are never shown less context than it used.
|
|
50
|
+
*
|
|
51
|
+
* Turns that produced no message are included and carry `failure`. Render
|
|
52
|
+
* them: a transcript that silently drops them reads as a customer being
|
|
53
|
+
* ignored, which is the opposite of what happened.
|
|
54
|
+
*
|
|
55
|
+
* Throws `ConnectError` with `code: "not_found"` for a thread this workspace
|
|
56
|
+
* does not hold.
|
|
57
|
+
*/
|
|
58
|
+
get(conversationId, options) {
|
|
59
|
+
return request(this.transport, {
|
|
60
|
+
method: "GET",
|
|
61
|
+
path: `/conversations/${encodeURIComponent(conversationId)}`,
|
|
62
|
+
query: { workspaceId: options.workspaceId },
|
|
63
|
+
headers: actingAs(options.asUser),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -9,6 +9,7 @@ import { SchedulingResource } from "./resources/scheduling.js";
|
|
|
9
9
|
import { ContactsResource } from "./resources/contacts.js";
|
|
10
10
|
import { ChannelsResource } from "./resources/channels.js";
|
|
11
11
|
import { CallsResource } from "./resources/calls.js";
|
|
12
|
+
import { ConversationsResource } from "./resources/conversations.js";
|
|
12
13
|
import { KvResource } from "./resources/kv.js";
|
|
13
14
|
import { BillingResource } from "./resources/billing.js";
|
|
14
15
|
import { DriveResource } from "./resources/drive.js";
|
|
@@ -88,6 +89,8 @@ export declare class ConnectClient {
|
|
|
88
89
|
readonly channels: ChannelsResource;
|
|
89
90
|
/** The calls that arrived on those channels, with their transcripts. */
|
|
90
91
|
readonly calls: CallsResource;
|
|
92
|
+
/** The message threads a subscribing workspace's agents are holding. */
|
|
93
|
+
readonly conversations: ConversationsResource;
|
|
91
94
|
/** RAG chat and structured extraction. */
|
|
92
95
|
readonly ai: AiResource;
|
|
93
96
|
readonly enrichment: EnrichmentResource;
|
|
@@ -123,4 +126,4 @@ export declare class ConnectClient {
|
|
|
123
126
|
constructor(options: ConnectClientOptions);
|
|
124
127
|
}
|
|
125
128
|
/** Kept in step with `package.json` by `sdk.test.ts`. */
|
|
126
|
-
export declare const SDK_VERSION = "0.
|
|
129
|
+
export declare const SDK_VERSION = "0.29.0";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type TransportOptions } from "../http.js";
|
|
2
|
+
import type { ConversationDetail, ConversationsPage } from "../../types.js";
|
|
3
|
+
export interface ListConversationsInput {
|
|
4
|
+
workspaceId: string;
|
|
5
|
+
/**
|
|
6
|
+
* Narrow to one party's threads.
|
|
7
|
+
*
|
|
8
|
+
* Any reasonable spelling - `+919000000001`, `919000000001`,
|
|
9
|
+
* `+91 90000 00001` all resolve to the same threads, because the number is
|
|
10
|
+
* normalized server-side before the match. This is the filter to reach for
|
|
11
|
+
* beside your own customer record: you hold a phone number, and you cannot
|
|
12
|
+
* derive a conversation id from one because the id is built from a *hashed*
|
|
13
|
+
* key.
|
|
14
|
+
*/
|
|
15
|
+
contactAddress?: string;
|
|
16
|
+
status?: "open" | "snoozed" | "closed";
|
|
17
|
+
/** From a previous page's `nextCursor`. */
|
|
18
|
+
cursor?: string;
|
|
19
|
+
/** 1-50, default 20. */
|
|
20
|
+
limit?: number;
|
|
21
|
+
/** See `ActingAsOption` on the scheduling resource - same contract. */
|
|
22
|
+
asUser?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* What a workspace's agents are saying in writing.
|
|
26
|
+
*
|
|
27
|
+
* **The sibling of `calls`, with one difference that decides how it is
|
|
28
|
+
* gated: these are the *workspace's* threads, not your application's.** A call
|
|
29
|
+
* arrived on a channel you hold, so `calls.list` is scoped to your
|
|
30
|
+
* application. A conversation lives in the workspace's own subtree and belongs
|
|
31
|
+
* to it the way its contact book does - so reading one needs a live
|
|
32
|
+
* subscription in that workspace, and, when the call comes from a browser
|
|
33
|
+
* through the bridge, the signed-in member's own `agent:read`.
|
|
34
|
+
*
|
|
35
|
+
* Read-only, and deliberately. Sending on a thread is not a missing verb:
|
|
36
|
+
* inside WhatsApp's 24-hour window a reply is the agent's to make, outside it
|
|
37
|
+
* only an approved template may be sent, and posting into a thread would mean
|
|
38
|
+
* saying which of those it was and what it means for the agent still
|
|
39
|
+
* answering.
|
|
40
|
+
*/
|
|
41
|
+
export declare class ConversationsResource {
|
|
42
|
+
private readonly transport;
|
|
43
|
+
constructor(transport: TransportOptions);
|
|
44
|
+
/**
|
|
45
|
+
* The workspace's threads, newest activity first.
|
|
46
|
+
*
|
|
47
|
+
* **Page until `hasMore` is `false`; do not stop on an empty `items`.** An
|
|
48
|
+
* inbox grows without bound, so there is no total count to page against.
|
|
49
|
+
*/
|
|
50
|
+
list(input: ListConversationsInput): Promise<ConversationsPage>;
|
|
51
|
+
/**
|
|
52
|
+
* One thread and its recent messages, oldest first.
|
|
53
|
+
*
|
|
54
|
+
* **Capped, not paged** - you get the same window the agent itself sees when
|
|
55
|
+
* it composes a reply, so you are never shown less context than it used.
|
|
56
|
+
*
|
|
57
|
+
* Turns that produced no message are included and carry `failure`. Render
|
|
58
|
+
* them: a transcript that silently drops them reads as a customer being
|
|
59
|
+
* ignored, which is the opposite of what happened.
|
|
60
|
+
*
|
|
61
|
+
* Throws `ConnectError` with `code: "not_found"` for a thread this workspace
|
|
62
|
+
* does not hold.
|
|
63
|
+
*/
|
|
64
|
+
get(conversationId: string, options: {
|
|
65
|
+
workspaceId: string;
|
|
66
|
+
asUser?: string;
|
|
67
|
+
}): Promise<ConversationDetail>;
|
|
68
|
+
}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -775,7 +775,7 @@ export interface EmitEventsResponse {
|
|
|
775
775
|
* `createConnectWebhookHandler` simply ignores anything you have not
|
|
776
776
|
* registered a handler for.
|
|
777
777
|
*/
|
|
778
|
-
export type ConnectWebhookEventType = "workspace.deletion_scheduled" | "workspace.deletion_cancelled" | "workspace.deleted" | "subscription.cancelled";
|
|
778
|
+
export type ConnectWebhookEventType = "workspace.deletion_scheduled" | "workspace.deletion_cancelled" | "workspace.deleted" | "subscription.cancelled" | "call.completed" | "call.missed" | "booking.confirmed" | "booking.cancelled" | "booking.rescheduled" | "contact.created" | "contact.updated";
|
|
779
779
|
/**
|
|
780
780
|
* A workspace's deletion has been confirmed and the grace window is running.
|
|
781
781
|
*
|
|
@@ -815,12 +815,197 @@ export interface SubscriptionCancelledEvent {
|
|
|
815
815
|
subscriptionId: string;
|
|
816
816
|
applicationId: string;
|
|
817
817
|
}
|
|
818
|
+
/** Which way a call went. */
|
|
819
|
+
export type ConnectCallDirection = "inbound" | "outbound";
|
|
820
|
+
/**
|
|
821
|
+
* A call ended.
|
|
822
|
+
*
|
|
823
|
+
* The event an external system of record is built on: it is what turns a
|
|
824
|
+
* conversation that happened in Connect's telephony into a row in your own
|
|
825
|
+
* timeline with nobody retyping it. Fires for every completed call, whether an
|
|
826
|
+
* agent answered it or a person did.
|
|
827
|
+
*/
|
|
828
|
+
export interface CallCompletedEvent {
|
|
829
|
+
workspaceId: string;
|
|
830
|
+
callId: string;
|
|
831
|
+
channelId: string | null;
|
|
832
|
+
/**
|
|
833
|
+
* E.164, and the only identity a call event carries.
|
|
834
|
+
*
|
|
835
|
+
* There is deliberately no `contactId`: Connect resolves a caller to a
|
|
836
|
+
* contact by number, and at the moment a call ends it may not have done so
|
|
837
|
+
* at all - the field would be `null` on most deliveries, which is a field
|
|
838
|
+
* that silently does nothing. Match on this instead. It is the same
|
|
839
|
+
* canonical form `contacts.lookupByPhone` takes, so resolving it is one call
|
|
840
|
+
* if you want the contact.
|
|
841
|
+
*/
|
|
842
|
+
contactPhone: string | null;
|
|
843
|
+
direction: ConnectCallDirection;
|
|
844
|
+
/** How it ended, as Connect's telephony reported it - `"completed"`, `"transferred"`, … */
|
|
845
|
+
outcome: string;
|
|
846
|
+
durationSeconds: number;
|
|
847
|
+
/**
|
|
848
|
+
* What the agent's skills captured during the conversation, keyed by the
|
|
849
|
+
* capture fields the agent declares.
|
|
850
|
+
*
|
|
851
|
+
* Shapeless on purpose - the keys and the value types are whatever that
|
|
852
|
+
* agent's `extractionSchema` declares, which Connect does not constrain.
|
|
853
|
+
* `{}` on most calls, since an unanswered or human-answered call extracts
|
|
854
|
+
* nothing, so treat every key as optional rather than checking the object
|
|
855
|
+
* for presence.
|
|
856
|
+
*/
|
|
857
|
+
extraction: Record<string, unknown>;
|
|
858
|
+
/** ISO-8601. */
|
|
859
|
+
occurredAt: string;
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* A call reached a channel and nothing answered it.
|
|
863
|
+
*
|
|
864
|
+
* Its own event rather than an `outcome` on the one above, because the action
|
|
865
|
+
* differs in kind: a completed call is something to file, a missed one is
|
|
866
|
+
* somebody to ring back. Treating this as a lead is the right thing to do
|
|
867
|
+
* with it.
|
|
868
|
+
*/
|
|
869
|
+
export interface CallMissedEvent {
|
|
870
|
+
workspaceId: string;
|
|
871
|
+
callId: string;
|
|
872
|
+
channelId: string | null;
|
|
873
|
+
/** E.164, and the only identity this carries - see `CallCompletedEvent.contactPhone`. */
|
|
874
|
+
contactPhone: string | null;
|
|
875
|
+
direction: ConnectCallDirection;
|
|
876
|
+
/** ISO-8601. */
|
|
877
|
+
occurredAt: string;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* An appointment was booked and is holding its slot.
|
|
881
|
+
*
|
|
882
|
+
* Raised however it was booked - the customer's portal, the public booking
|
|
883
|
+
* page, a voice agent mid-call, or your own API call - so you get one shape
|
|
884
|
+
* for all four and never have to reconcile "did we create this, or did they?".
|
|
885
|
+
* Dedupe on `bookingId`, not on the envelope alone.
|
|
886
|
+
*/
|
|
887
|
+
export interface BookingConfirmedEvent {
|
|
888
|
+
workspaceId: string;
|
|
889
|
+
bookingId: string;
|
|
890
|
+
eventTypeId: string;
|
|
891
|
+
eventTypeName: string;
|
|
892
|
+
/** ISO-8601, UTC. Render it in `timezone`, which is the workspace's, not the viewer's. */
|
|
893
|
+
startAt: string;
|
|
894
|
+
endAt: string;
|
|
895
|
+
/** IANA zone the appointment was booked in. */
|
|
896
|
+
timezone: string;
|
|
897
|
+
attendeeName: string;
|
|
898
|
+
attendeeEmail: string | null;
|
|
899
|
+
/** E.164. */
|
|
900
|
+
attendeePhone: string | null;
|
|
901
|
+
contactId: string | null;
|
|
902
|
+
/** Connect user ids of the hosts. */
|
|
903
|
+
hostMemberIds: string[];
|
|
904
|
+
/** ISO-8601. */
|
|
905
|
+
occurredAt: string;
|
|
906
|
+
}
|
|
907
|
+
/** An appointment was cancelled. The slot is free and any prep against it is void. */
|
|
908
|
+
export interface BookingCancelledEvent {
|
|
909
|
+
workspaceId: string;
|
|
910
|
+
bookingId: string;
|
|
911
|
+
eventTypeId: string;
|
|
912
|
+
eventTypeName: string;
|
|
913
|
+
/** ISO-8601. The time it *was* going to happen. */
|
|
914
|
+
startAt: string;
|
|
915
|
+
attendeeName: string;
|
|
916
|
+
contactId: string | null;
|
|
917
|
+
/** ISO-8601. */
|
|
918
|
+
occurredAt: string;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* An appointment moved.
|
|
922
|
+
*
|
|
923
|
+
* Connect retires the old booking and writes a new one rather than mutating
|
|
924
|
+
* it, so this carries **both ids**: `bookingId` is the row that now exists and
|
|
925
|
+
* `previousBookingId` is the one you are already holding. Reconcile on the
|
|
926
|
+
* previous id or you will end up with two appointments.
|
|
927
|
+
*/
|
|
928
|
+
export interface BookingRescheduledEvent {
|
|
929
|
+
workspaceId: string;
|
|
930
|
+
bookingId: string;
|
|
931
|
+
eventTypeId: string;
|
|
932
|
+
eventTypeName: string;
|
|
933
|
+
/** ISO-8601, the new time. */
|
|
934
|
+
startAt: string;
|
|
935
|
+
endAt: string;
|
|
936
|
+
previousBookingId: string;
|
|
937
|
+
/** ISO-8601, the time it was before. */
|
|
938
|
+
previousStartAt: string;
|
|
939
|
+
timezone: string;
|
|
940
|
+
attendeeName: string;
|
|
941
|
+
contactId: string | null;
|
|
942
|
+
/** ISO-8601. */
|
|
943
|
+
occurredAt: string;
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* How Connect came to know a contact.
|
|
947
|
+
*
|
|
948
|
+
* `extraction` is the interesting one: an agent's skill captured them from
|
|
949
|
+
* what was said, with nobody typing anything. `call` means the number was seen
|
|
950
|
+
* on a call. `manual` and `import` are the two a human stands behind, and
|
|
951
|
+
* Connect lets only those overwrite a field that already has a value.
|
|
952
|
+
*/
|
|
953
|
+
export type ConnectContactSource = "call" | "manual" | "import" | "api" | "extraction";
|
|
954
|
+
/**
|
|
955
|
+
* Connect learned about a person.
|
|
956
|
+
*
|
|
957
|
+
* The event with no equivalent anywhere else in this API: most often an agent
|
|
958
|
+
* captured them mid-call through the `contact-capture` skill, so the contact
|
|
959
|
+
* exists in Connect and your product has never heard of them. Without this the
|
|
960
|
+
* capture is invisible where the customer actually works.
|
|
961
|
+
*/
|
|
962
|
+
export interface ContactCreatedEvent {
|
|
963
|
+
workspaceId: string;
|
|
964
|
+
contactId: string;
|
|
965
|
+
name: string | null;
|
|
966
|
+
/** E.164. */
|
|
967
|
+
phone: string | null;
|
|
968
|
+
email: string | null;
|
|
969
|
+
company: string | null;
|
|
970
|
+
source: ConnectContactSource;
|
|
971
|
+
/** ISO-8601. */
|
|
972
|
+
occurredAt: string;
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* A known contact's details changed.
|
|
976
|
+
*
|
|
977
|
+
* `changed` names the fields that actually moved, so you can apply a patch
|
|
978
|
+
* rather than overwriting fields your own users may have curated - a customer
|
|
979
|
+
* who corrected a company name in your product should not have it replaced by
|
|
980
|
+
* what an agent heard over a bad line.
|
|
981
|
+
*/
|
|
982
|
+
export interface ContactUpdatedEvent {
|
|
983
|
+
workspaceId: string;
|
|
984
|
+
contactId: string;
|
|
985
|
+
name: string | null;
|
|
986
|
+
/** E.164. */
|
|
987
|
+
phone: string | null;
|
|
988
|
+
email: string | null;
|
|
989
|
+
company: string | null;
|
|
990
|
+
/** The field names that changed on this update. */
|
|
991
|
+
changed: string[];
|
|
992
|
+
source: ConnectContactSource;
|
|
993
|
+
/** ISO-8601. */
|
|
994
|
+
occurredAt: string;
|
|
995
|
+
}
|
|
818
996
|
/** Maps each event type to the shape of its `data`. */
|
|
819
997
|
export interface ConnectWebhookEventMap {
|
|
820
998
|
"workspace.deletion_scheduled": WorkspaceDeletionScheduledEvent;
|
|
821
999
|
"workspace.deletion_cancelled": WorkspaceDeletionCancelledEvent;
|
|
822
1000
|
"workspace.deleted": WorkspaceDeletedEvent;
|
|
823
1001
|
"subscription.cancelled": SubscriptionCancelledEvent;
|
|
1002
|
+
"call.completed": CallCompletedEvent;
|
|
1003
|
+
"call.missed": CallMissedEvent;
|
|
1004
|
+
"booking.confirmed": BookingConfirmedEvent;
|
|
1005
|
+
"booking.cancelled": BookingCancelledEvent;
|
|
1006
|
+
"booking.rescheduled": BookingRescheduledEvent;
|
|
1007
|
+
"contact.created": ContactCreatedEvent;
|
|
1008
|
+
"contact.updated": ContactUpdatedEvent;
|
|
824
1009
|
}
|
|
825
1010
|
/** The envelope, as it arrives on the wire. */
|
|
826
1011
|
export interface ConnectWebhookEnvelope<T extends ConnectWebhookEventType = ConnectWebhookEventType> {
|
|
@@ -1637,3 +1822,85 @@ export interface CallsPage {
|
|
|
1637
1822
|
nextCursor: string | null;
|
|
1638
1823
|
hasMore: boolean;
|
|
1639
1824
|
}
|
|
1825
|
+
/**
|
|
1826
|
+
* One message thread a workspace's agents are holding.
|
|
1827
|
+
*
|
|
1828
|
+
* Hand-written rather than imported from Connect, like every other type in
|
|
1829
|
+
* this file - `conformance.test.ts` type-checks it against the server DTO, so
|
|
1830
|
+
* drift fails the build here rather than in your integration.
|
|
1831
|
+
*
|
|
1832
|
+
* Dates are ISO-8601 strings. JSON has no date type, and a UTC ISO string
|
|
1833
|
+
* sorts lexicographically in chronological order, so these compare and
|
|
1834
|
+
* range-filter without being parsed first.
|
|
1835
|
+
*/
|
|
1836
|
+
export interface ConnectConversation {
|
|
1837
|
+
id: string;
|
|
1838
|
+
/** The channel the thread arrived on. */
|
|
1839
|
+
channelId: string;
|
|
1840
|
+
/** Today always `whatsapp` - a voice channel produces calls, not threads. */
|
|
1841
|
+
kind: "whatsapp";
|
|
1842
|
+
/** The other party, normalized to E.164. */
|
|
1843
|
+
contactAddress: string;
|
|
1844
|
+
/**
|
|
1845
|
+
* The Connect contact this thread is about, once one has been matched.
|
|
1846
|
+
*
|
|
1847
|
+
* The join to your own records - store this on yours rather than matching
|
|
1848
|
+
* phone numbers. See `ContactsResource`.
|
|
1849
|
+
*/
|
|
1850
|
+
contactId: string | null;
|
|
1851
|
+
/** As the provider reported it, when it did. */
|
|
1852
|
+
contactName: string | null;
|
|
1853
|
+
status: "open" | "snoozed" | "closed";
|
|
1854
|
+
/** `null` means nothing is answering: no routing, or a human took over. */
|
|
1855
|
+
agentId: string | null;
|
|
1856
|
+
lastInboundAt: string;
|
|
1857
|
+
lastOutboundAt: string | null;
|
|
1858
|
+
lastMessagePreview: string;
|
|
1859
|
+
/**
|
|
1860
|
+
* Whether a free-form reply is still allowed, computed against **Connect's**
|
|
1861
|
+
* clock.
|
|
1862
|
+
*
|
|
1863
|
+
* WhatsApp allows one only inside 24 hours of the customer's last message;
|
|
1864
|
+
* outside it, only an approved template may be sent. Do not re-derive this
|
|
1865
|
+
* from `lastInboundAt` against your own clock - a skew shows up as a thread
|
|
1866
|
+
* your UI calls answerable when it is not.
|
|
1867
|
+
*/
|
|
1868
|
+
canReplyFreely: boolean;
|
|
1869
|
+
createdAt: string;
|
|
1870
|
+
updatedAt: string;
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Why a turn produced no message.
|
|
1874
|
+
*
|
|
1875
|
+
* `refused` is not a failure at all - it is the agent deciding not to answer,
|
|
1876
|
+
* which is what it is for.
|
|
1877
|
+
*/
|
|
1878
|
+
export type ConnectMessageFailure = "window_closed" | "no_agent" | "model_error" | "send_failed" | "refused";
|
|
1879
|
+
/** One message on a thread. A turn that sent nothing still appears, carrying `failure`. */
|
|
1880
|
+
export interface ConnectConversationMessage {
|
|
1881
|
+
id: string;
|
|
1882
|
+
direction: "inbound" | "outbound";
|
|
1883
|
+
/**
|
|
1884
|
+
* Who produced it.
|
|
1885
|
+
*
|
|
1886
|
+
* `agent` and `human` are both outbound and deliberately distinguished - a
|
|
1887
|
+
* thread that cannot show which replies were automatic is one nobody trusts
|
|
1888
|
+
* enough to leave running. `system` is neither party: a turn Connect itself
|
|
1889
|
+
* recorded, which is what a `failure` row is.
|
|
1890
|
+
*/
|
|
1891
|
+
author: "contact" | "agent" | "human" | "system";
|
|
1892
|
+
text: string;
|
|
1893
|
+
failure: ConnectMessageFailure | null;
|
|
1894
|
+
createdAt: string;
|
|
1895
|
+
}
|
|
1896
|
+
/** A thread and its recent messages, oldest first. Capped rather than paged. */
|
|
1897
|
+
export interface ConversationDetail {
|
|
1898
|
+
conversation: ConnectConversation;
|
|
1899
|
+
messages: ConnectConversationMessage[];
|
|
1900
|
+
}
|
|
1901
|
+
/** One page of threads, newest activity first. Page until `hasMore` is `false`. */
|
|
1902
|
+
export interface ConversationsPage {
|
|
1903
|
+
items: ConnectConversation[];
|
|
1904
|
+
nextCursor: string | null;
|
|
1905
|
+
hasMore: boolean;
|
|
1906
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmoops/connect-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Official server-side SDK for the CosmoOps Connect platform API - Login with Connect, entitlements, billing and metering.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "commonjs",
|