@cosmoops/connect-sdk 0.18.0 → 0.19.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 CHANGED
@@ -3,6 +3,48 @@
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.19.0
7
+
8
+ ### Added
9
+
10
+ - **`connect.contacts`** - who a workspace already knows how to reach.
11
+
12
+ ```ts
13
+ const contact = await connect.contacts.lookupByPhone({ workspaceId, phone });
14
+ if (contact) {
15
+ // A caller this workspace has met before.
16
+ }
17
+ ```
18
+
19
+ `lookupByPhone` is one point read, cheap enough for a screen pop on every
20
+ inbound call, and resolves to `null` for a first-time caller - the ordinary
21
+ case, not an error. `get(contactId, { workspaceId })` reads the record behind
22
+ an id you already hold. Needs a key scoped to `contacts:read` and a live
23
+ subscription in the workspace.
24
+
25
+ **This is the telephony identity, not a CRM record.** A contact is keyed by
26
+ `primaryPhone` - the one fact every inbound call carries - and it is written
27
+ automatically: a voice agent creates one on the first call from a new number
28
+ and fills fields in as it learns them. `source` tells you which kind you are
29
+ holding (`call` means an agent guessed and nobody has confirmed), and
30
+ `phones[].verified` tells you the same about a single value.
31
+
32
+ **Reference it, do not copy it.** If your application keeps its own customer
33
+ records, store this contact's `id` on yours. Two writable copies of a
34
+ person's name with no single owner drift, and Connect's is the one an agent
35
+ keeps updating from calls. Storing the id also makes `Booking.contactId` an
36
+ **exact** join - so "which of my records is this appointment for" is a lookup
37
+ rather than a phone-number match, including for appointments a voice agent
38
+ booked where nobody ever chose one of your records.
39
+
40
+ Read-only, deliberately. Writing into a store with a per-field provenance
41
+ chain means declaring where your facts came from and how they merge with a
42
+ human's; nothing needs that yet.
43
+
44
+ - New exported types: `ConnectContact`, `ContactChannelValue`, `ContactSource`,
45
+ and `ContactsResource` / `LookupContactInput` from
46
+ `@cosmoops/connect-sdk/server`.
47
+
6
48
  ## 0.18.0
7
49
 
8
50
  ### Added
package/README.md CHANGED
@@ -342,6 +342,7 @@ try {
342
342
  | `entitlements` | `getReadiness` · `getSubscription` · `hasFeature` |
343
343
  | `billing` | `listPlans` · `getAddonUsage` · `consumeAddon` |
344
344
  | `drive` | `upload` · `getFileUrl` · `getPreviewUrl` |
345
+ | `contacts` | `lookupByPhone` · `get` |
345
346
  | `scheduling` | `listEventTypes` · `findSlots` · `listBookings` · `createBooking` · `rescheduleBooking` · `cancelBooking` |
346
347
  | `ai` | `chat` · `extract` |
347
348
  | `otp` | `send` · `verify` |
@@ -7,6 +7,7 @@ const ai_js_1 = require("./resources/ai.js");
7
7
  const data_js_1 = require("./resources/data.js");
8
8
  const workspaces_js_1 = require("./resources/workspaces.js");
9
9
  const scheduling_js_1 = require("./resources/scheduling.js");
10
+ const contacts_js_1 = require("./resources/contacts.js");
10
11
  const kv_js_1 = require("./resources/kv.js");
11
12
  const billing_js_1 = require("./resources/billing.js");
12
13
  const drive_js_1 = require("./resources/drive.js");
@@ -82,6 +83,8 @@ class ConnectClient {
82
83
  drive;
83
84
  /** A subscribing workspace's own calendar - what is bookable, free, and booked. */
84
85
  scheduling;
86
+ /** Who a workspace already knows how to reach, by phone or by id. */
87
+ contacts;
85
88
  /** RAG chat and structured extraction. */
86
89
  ai;
87
90
  /** Your application's own document store - see `DataResource`. */
@@ -131,6 +134,7 @@ class ConnectClient {
131
134
  this.billing = new billing_js_1.BillingResource(transport);
132
135
  this.drive = new drive_js_1.DriveResource(transport);
133
136
  this.scheduling = new scheduling_js_1.SchedulingResource(transport);
137
+ this.contacts = new contacts_js_1.ContactsResource(transport);
134
138
  this.ai = new ai_js_1.AiResource(transport);
135
139
  this.data = new data_js_1.DataResource(transport);
136
140
  this.kv = new kv_js_1.KvResource(transport);
@@ -146,4 +150,4 @@ class ConnectClient {
146
150
  }
147
151
  exports.ConnectClient = ConnectClient;
148
152
  /** Kept in step with `package.json` by `sdk.test.ts`. */
149
- exports.SDK_VERSION = "0.18.0";
153
+ exports.SDK_VERSION = "0.19.0";
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ContactsResource = void 0;
4
+ const http_js_1 = require("../http.js");
5
+ const acting_as_js_1 = require("./acting-as.js");
6
+ /**
7
+ * Who a workspace already knows how to reach.
8
+ *
9
+ * **This is the telephony identity, not a CRM record.** A contact is keyed by
10
+ * `primaryPhone` - the one fact every inbound call carries - and it is written
11
+ * automatically: a voice agent creates one on the first call from a new number
12
+ * and fills fields in as it learns them. `source` says which kind you are
13
+ * holding, and it is worth rendering: `call` means an agent guessed and nobody
14
+ * has confirmed, `manual` means a person typed it.
15
+ *
16
+ * **Reference it, do not copy it.** If your application keeps its own customer
17
+ * records, store this contact's `id` on yours rather than duplicating the
18
+ * fields. Two writable copies of a person's name with no single owner drift,
19
+ * and Connect's is the one an agent keeps updating from calls. The id is also
20
+ * an exact join to anything else keyed by it - `Booking.contactId`, most
21
+ * usefully - so "which of my records is this appointment for" becomes a lookup
22
+ * instead of a phone match.
23
+ *
24
+ * Read-only for now. Writing into a store with a per-field provenance chain
25
+ * means declaring where your facts came from and how they merge with a
26
+ * human's, and nothing needs that yet.
27
+ */
28
+ class ContactsResource {
29
+ transport;
30
+ constructor(transport) {
31
+ this.transport = transport;
32
+ }
33
+ /**
34
+ * "Do we already know this number?" - one point read, cheap enough for a
35
+ * screen pop on every inbound call.
36
+ *
37
+ * Resolves to `null` for a number nobody has called from before. That is the
38
+ * ordinary case for a first-time caller, not an error, which is why it is a
39
+ * null rather than a thrown `ConnectError`.
40
+ */
41
+ async lookupByPhone(input) {
42
+ const result = await (0, http_js_1.request)(this.transport, {
43
+ method: "GET",
44
+ path: "/contacts",
45
+ query: { workspaceId: input.workspaceId, phone: input.phone },
46
+ headers: (0, acting_as_js_1.actingAs)(input.asUser),
47
+ });
48
+ return result.contact ?? null;
49
+ }
50
+ /**
51
+ * The record behind an id you already hold.
52
+ *
53
+ * Throws `ConnectError` with `code: "not_found"` for an id that does not
54
+ * exist *or* belongs to another workspace - deliberately the same answer,
55
+ * since telling those apart would confirm a contact exists somewhere else.
56
+ */
57
+ get(contactId, options) {
58
+ return (0, http_js_1.request)(this.transport, {
59
+ method: "GET",
60
+ path: `/contacts/${encodeURIComponent(contactId)}`,
61
+ query: { workspaceId: options.workspaceId },
62
+ headers: (0, acting_as_js_1.actingAs)(options.asUser),
63
+ });
64
+ }
65
+ }
66
+ exports.ContactsResource = ContactsResource;
@@ -4,6 +4,7 @@ import { AiResource } from "./resources/ai.js";
4
4
  import { DataResource, storePassthrough } from "./resources/data.js";
5
5
  import { WorkspacesResource } from "./resources/workspaces.js";
6
6
  import { SchedulingResource } from "./resources/scheduling.js";
7
+ import { ContactsResource } from "./resources/contacts.js";
7
8
  import { KvResource } from "./resources/kv.js";
8
9
  import { BillingResource } from "./resources/billing.js";
9
10
  import { DriveResource } from "./resources/drive.js";
@@ -79,6 +80,8 @@ export class ConnectClient {
79
80
  drive;
80
81
  /** A subscribing workspace's own calendar - what is bookable, free, and booked. */
81
82
  scheduling;
83
+ /** Who a workspace already knows how to reach, by phone or by id. */
84
+ contacts;
82
85
  /** RAG chat and structured extraction. */
83
86
  ai;
84
87
  /** Your application's own document store - see `DataResource`. */
@@ -128,6 +131,7 @@ export class ConnectClient {
128
131
  this.billing = new BillingResource(transport);
129
132
  this.drive = new DriveResource(transport);
130
133
  this.scheduling = new SchedulingResource(transport);
134
+ this.contacts = new ContactsResource(transport);
131
135
  this.ai = new AiResource(transport);
132
136
  this.data = new DataResource(transport);
133
137
  this.kv = new KvResource(transport);
@@ -142,4 +146,4 @@ export class ConnectClient {
142
146
  }
143
147
  }
144
148
  /** Kept in step with `package.json` by `sdk.test.ts`. */
145
- export const SDK_VERSION = "0.18.0";
149
+ export const SDK_VERSION = "0.19.0";
@@ -0,0 +1,62 @@
1
+ import { request } from "../http.js";
2
+ import { actingAs } from "./acting-as.js";
3
+ /**
4
+ * Who a workspace already knows how to reach.
5
+ *
6
+ * **This is the telephony identity, not a CRM record.** A contact is keyed by
7
+ * `primaryPhone` - the one fact every inbound call carries - and it is written
8
+ * automatically: a voice agent creates one on the first call from a new number
9
+ * and fills fields in as it learns them. `source` says which kind you are
10
+ * holding, and it is worth rendering: `call` means an agent guessed and nobody
11
+ * has confirmed, `manual` means a person typed it.
12
+ *
13
+ * **Reference it, do not copy it.** If your application keeps its own customer
14
+ * records, store this contact's `id` on yours rather than duplicating the
15
+ * fields. Two writable copies of a person's name with no single owner drift,
16
+ * and Connect's is the one an agent keeps updating from calls. The id is also
17
+ * an exact join to anything else keyed by it - `Booking.contactId`, most
18
+ * usefully - so "which of my records is this appointment for" becomes a lookup
19
+ * instead of a phone match.
20
+ *
21
+ * Read-only for now. Writing into a store with a per-field provenance chain
22
+ * means declaring where your facts came from and how they merge with a
23
+ * human's, and nothing needs that yet.
24
+ */
25
+ export class ContactsResource {
26
+ transport;
27
+ constructor(transport) {
28
+ this.transport = transport;
29
+ }
30
+ /**
31
+ * "Do we already know this number?" - one point read, cheap enough for a
32
+ * screen pop on every inbound call.
33
+ *
34
+ * Resolves to `null` for a number nobody has called from before. That is the
35
+ * ordinary case for a first-time caller, not an error, which is why it is a
36
+ * null rather than a thrown `ConnectError`.
37
+ */
38
+ async lookupByPhone(input) {
39
+ const result = await request(this.transport, {
40
+ method: "GET",
41
+ path: "/contacts",
42
+ query: { workspaceId: input.workspaceId, phone: input.phone },
43
+ headers: actingAs(input.asUser),
44
+ });
45
+ return result.contact ?? null;
46
+ }
47
+ /**
48
+ * The record behind an id you already hold.
49
+ *
50
+ * Throws `ConnectError` with `code: "not_found"` for an id that does not
51
+ * exist *or* belongs to another workspace - deliberately the same answer,
52
+ * since telling those apart would confirm a contact exists somewhere else.
53
+ */
54
+ get(contactId, options) {
55
+ return request(this.transport, {
56
+ method: "GET",
57
+ path: `/contacts/${encodeURIComponent(contactId)}`,
58
+ query: { workspaceId: options.workspaceId },
59
+ headers: actingAs(options.asUser),
60
+ });
61
+ }
62
+ }
@@ -5,6 +5,7 @@ import { AiResource } from "./resources/ai.js";
5
5
  import { DataResource } from "./resources/data.js";
6
6
  import { WorkspacesResource } from "./resources/workspaces.js";
7
7
  import { SchedulingResource } from "./resources/scheduling.js";
8
+ import { ContactsResource } from "./resources/contacts.js";
8
9
  import { KvResource } from "./resources/kv.js";
9
10
  import { BillingResource } from "./resources/billing.js";
10
11
  import { DriveResource } from "./resources/drive.js";
@@ -78,6 +79,8 @@ export declare class ConnectClient {
78
79
  readonly drive: DriveResource;
79
80
  /** A subscribing workspace's own calendar - what is bookable, free, and booked. */
80
81
  readonly scheduling: SchedulingResource;
82
+ /** Who a workspace already knows how to reach, by phone or by id. */
83
+ readonly contacts: ContactsResource;
81
84
  /** RAG chat and structured extraction. */
82
85
  readonly ai: AiResource;
83
86
  /** Your application's own document store - see `DataResource`. */
@@ -112,4 +115,4 @@ export declare class ConnectClient {
112
115
  constructor(options: ConnectClientOptions);
113
116
  }
114
117
  /** Kept in step with `package.json` by `sdk.test.ts`. */
115
- export declare const SDK_VERSION = "0.18.0";
118
+ export declare const SDK_VERSION = "0.19.0";
@@ -28,6 +28,7 @@ export type { SessionsResource } from "./resources/sessions.js";
28
28
  export type { ExecuteIntegrationActionInput, IntegrationsResource } from "./resources/integrations.js";
29
29
  export type { PlatformResource } from "./resources/platform.js";
30
30
  export type { SchedulingResource, FindSlotsInput, ListBookingsInput, CreateBookingInput, RescheduleBookingInput, } from "./resources/scheduling.js";
31
+ export type { ContactsResource, LookupContactInput } from "./resources/contacts.js";
31
32
  /**
32
33
  * Webhook verification and a ready-made POST route. Server-only for the
33
34
  * obvious reason: the signing secret must never reach a browser bundle.
@@ -0,0 +1,60 @@
1
+ import { type TransportOptions } from "../http.js";
2
+ import type { ConnectContact } from "../../types.js";
3
+ export interface LookupContactInput {
4
+ workspaceId: string;
5
+ /**
6
+ * Any reasonable spelling. `+919000000001`, `919000000001` and
7
+ * `+91 90000 00001` all resolve to the same contact - the number is
8
+ * normalized to E.164 before the lookup, because comparing unnormalized is
9
+ * how a contact that exists reads as absent.
10
+ */
11
+ phone: string;
12
+ /** See `ActingAsOption` on the scheduling resource - same contract. */
13
+ asUser?: string;
14
+ }
15
+ /**
16
+ * Who a workspace already knows how to reach.
17
+ *
18
+ * **This is the telephony identity, not a CRM record.** A contact is keyed by
19
+ * `primaryPhone` - the one fact every inbound call carries - and it is written
20
+ * automatically: a voice agent creates one on the first call from a new number
21
+ * and fills fields in as it learns them. `source` says which kind you are
22
+ * holding, and it is worth rendering: `call` means an agent guessed and nobody
23
+ * has confirmed, `manual` means a person typed it.
24
+ *
25
+ * **Reference it, do not copy it.** If your application keeps its own customer
26
+ * records, store this contact's `id` on yours rather than duplicating the
27
+ * fields. Two writable copies of a person's name with no single owner drift,
28
+ * and Connect's is the one an agent keeps updating from calls. The id is also
29
+ * an exact join to anything else keyed by it - `Booking.contactId`, most
30
+ * usefully - so "which of my records is this appointment for" becomes a lookup
31
+ * instead of a phone match.
32
+ *
33
+ * Read-only for now. Writing into a store with a per-field provenance chain
34
+ * means declaring where your facts came from and how they merge with a
35
+ * human's, and nothing needs that yet.
36
+ */
37
+ export declare class ContactsResource {
38
+ private readonly transport;
39
+ constructor(transport: TransportOptions);
40
+ /**
41
+ * "Do we already know this number?" - one point read, cheap enough for a
42
+ * screen pop on every inbound call.
43
+ *
44
+ * Resolves to `null` for a number nobody has called from before. That is the
45
+ * ordinary case for a first-time caller, not an error, which is why it is a
46
+ * null rather than a thrown `ConnectError`.
47
+ */
48
+ lookupByPhone(input: LookupContactInput): Promise<ConnectContact | null>;
49
+ /**
50
+ * The record behind an id you already hold.
51
+ *
52
+ * Throws `ConnectError` with `code: "not_found"` for an id that does not
53
+ * exist *or* belongs to another workspace - deliberately the same answer,
54
+ * since telling those apart would confirm a contact exists somewhere else.
55
+ */
56
+ get(contactId: string, options: {
57
+ workspaceId: string;
58
+ asUser?: string;
59
+ }): Promise<ConnectContact>;
60
+ }
@@ -1224,3 +1224,48 @@ export interface ListBookingsPage {
1224
1224
  hasMore: boolean;
1225
1225
  totalCount: number;
1226
1226
  }
1227
+ /**
1228
+ * A phone or email on a contact.
1229
+ *
1230
+ * `verified` means a human confirmed it reaches them - only a manual source
1231
+ * may ever set it. Branch on it: an unverified value is one an extraction
1232
+ * heard on a call and nobody has checked since.
1233
+ */
1234
+ export interface ContactChannelValue {
1235
+ value: string;
1236
+ label: string | null;
1237
+ verified: boolean;
1238
+ }
1239
+ /**
1240
+ * How a contact came to exist. `call` means a voice agent created it from an
1241
+ * inbound call; `manual` and `import` are the two a human stands behind.
1242
+ */
1243
+ export type ContactSource = "call" | "manual" | "import" | "api" | "extraction";
1244
+ /**
1245
+ * Someone a workspace knows how to reach - **the telephony identity**, not a
1246
+ * CRM record. See `ContactsResource` for why the difference decides whether
1247
+ * you reference this or copy it.
1248
+ */
1249
+ export interface ConnectContact {
1250
+ id: string;
1251
+ /** E.164, and immutable - the key this contact is identified by. */
1252
+ primaryPhone: string;
1253
+ phones: ContactChannelValue[];
1254
+ emails: ContactChannelValue[];
1255
+ name: {
1256
+ display: string | null;
1257
+ given: string | null;
1258
+ family: string | null;
1259
+ };
1260
+ business: {
1261
+ company: string | null;
1262
+ role: string | null;
1263
+ gstin: string | null;
1264
+ website: string | null;
1265
+ address: string | null;
1266
+ };
1267
+ tags: string[];
1268
+ source: ContactSource;
1269
+ createdAt: string;
1270
+ updatedAt: string;
1271
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmoops/connect-sdk",
3
- "version": "0.18.0",
3
+ "version": "0.19.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",