@cosmoops/connect-sdk 0.16.0 → 0.17.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 +76 -0
- package/README.md +34 -0
- package/dist/cjs/server/client.js +5 -1
- package/dist/cjs/server/resources/scheduling.js +122 -0
- package/dist/esm/server/client.js +5 -1
- package/dist/esm/server/resources/scheduling.js +118 -0
- package/dist/types/mcp/index.d.ts +14 -3
- package/dist/types/server/client.d.ts +4 -1
- package/dist/types/server/index.d.ts +1 -0
- package/dist/types/server/resources/scheduling.d.ts +91 -0
- package/dist/types/types.d.ts +71 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,82 @@
|
|
|
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.17.0
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **`connect.scheduling.*` - a subscribing workspace's own calendar.**
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
const eventTypes = await connect.scheduling.listEventTypes(workspaceId);
|
|
14
|
+
const { slots, timezone } = await connect.scheduling.findSlots({
|
|
15
|
+
workspaceId,
|
|
16
|
+
eventTypeId: eventTypes[0].id,
|
|
17
|
+
from: new Date(),
|
|
18
|
+
to: nextWeek,
|
|
19
|
+
});
|
|
20
|
+
const booking = await connect.scheduling.createBooking({
|
|
21
|
+
workspaceId,
|
|
22
|
+
eventTypeId: eventTypes[0].id,
|
|
23
|
+
startAt: slots[0].startAt,
|
|
24
|
+
attendee: { name: "Priya", phone: "+919000000001" },
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Five methods - `listEventTypes`, `findSlots`, `listBookings`,
|
|
29
|
+
`createBooking`, `cancelBooking` - against the same calendar the workspace's
|
|
30
|
+
own team configures and its voice agent books into. A booking you make shows
|
|
31
|
+
up on their screen immediately, and a slot their receptionist just took is
|
|
32
|
+
gone from your next `findSlots`. That is the reason to read it through
|
|
33
|
+
Connect rather than keeping a copy: two calendars double-book, and nothing
|
|
34
|
+
reconciles them afterwards.
|
|
35
|
+
|
|
36
|
+
Needs a key scoped to `calendar:read` (and `calendar:write` to book), **and**
|
|
37
|
+
a live subscription to your application in the workspace you name. The key
|
|
38
|
+
says which application is calling; the subscription is what makes you welcome
|
|
39
|
+
in a given tenant. A workspace that has not installed your application
|
|
40
|
+
answers `403` with the same message as an unknown workspace id.
|
|
41
|
+
|
|
42
|
+
Two things to hold onto when integrating:
|
|
43
|
+
|
|
44
|
+
- **`findSlots` is advisory.** Between reading it and booking, anyone else on
|
|
45
|
+
that calendar can take a slot - a member, a voice agent mid-call, someone on
|
|
46
|
+
the workspace's public booking link. `createBooking` re-checks inside a
|
|
47
|
+
transaction and throws a `ConnectError` with `code: "conflict"` rather than
|
|
48
|
+
double-booking. Handle that as an ordinary outcome and offer the next time;
|
|
49
|
+
it is not an error condition.
|
|
50
|
+
- **`timezone` is the business's, not the viewer's.** It is what to render a
|
|
51
|
+
slot's wall time in. A slot shown in the viewer's zone is an hour the
|
|
52
|
+
business may not be open.
|
|
53
|
+
|
|
54
|
+
- New exported types: `BookableEventType`, `BookingSlot`, `BookingSlots`,
|
|
55
|
+
`Booking`, `BookingAttendee`, `BookingStatus`, `BookingSource`,
|
|
56
|
+
`ListBookingsPage`, and the `SchedulingResource` input types from
|
|
57
|
+
`@cosmoops/connect-sdk/server`.
|
|
58
|
+
|
|
59
|
+
- `ConnectError.code` can now be `"conflict"` - today only from
|
|
60
|
+
`createBooking`. Nothing that switched on the previous codes stops
|
|
61
|
+
compiling: `code` is `string`, deliberately, so a new one reaches a partner
|
|
62
|
+
as a case to handle rather than a type error on a published package they
|
|
63
|
+
cannot patch.
|
|
64
|
+
|
|
65
|
+
## 0.16.1
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- **`McpToolResult` is a type alias, not an interface** — so `callTool()` can be
|
|
70
|
+
returned straight from `server.setRequestHandler(CallToolRequestSchema, ...)`.
|
|
71
|
+
|
|
72
|
+
The MCP SDK types a request handler's return as a union whose members carry an
|
|
73
|
+
index signature. TypeScript does not grant an interface the implicit index
|
|
74
|
+
signature it grants a type alias, so as an interface the result was assignable
|
|
75
|
+
to none of them, and the four-line wiring in 0.16.0's own documentation failed
|
|
76
|
+
to compile with a misleading `Property 'task' is missing`. The only way past it
|
|
77
|
+
was a cast, at exactly the boundary where a cast hides shape errors.
|
|
78
|
+
|
|
79
|
+
Type-level only; no runtime change, and nothing that compiled against 0.16.0
|
|
80
|
+
stops compiling.
|
|
81
|
+
|
|
6
82
|
## 0.16.0
|
|
7
83
|
|
|
8
84
|
### 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
|
+
| `scheduling` | `listEventTypes` · `findSlots` · `listBookings` · `createBooking` · `cancelBooking` |
|
|
345
346
|
| `ai` | `chat` · `extract` |
|
|
346
347
|
| `otp` | `send` · `verify` |
|
|
347
348
|
| `logs` | `write` |
|
|
@@ -349,6 +350,39 @@ try {
|
|
|
349
350
|
| `sessions` | `list` · `revoke` · `revokeOthers` |
|
|
350
351
|
| `integrations` | `list` · `initiateConnect` · `disconnect` · `execute` |
|
|
351
352
|
|
|
353
|
+
### Booking on a workspace's calendar
|
|
354
|
+
|
|
355
|
+
One calendar per workspace - the same one their team configures, and the same
|
|
356
|
+
one a voice agent books into. Needs `calendar:read` (plus `calendar:write` to
|
|
357
|
+
book) and a live subscription to your application in that workspace.
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
const eventTypes = await client.scheduling.listEventTypes(workspaceId);
|
|
361
|
+
const { slots, timezone } = await client.scheduling.findSlots({
|
|
362
|
+
workspaceId,
|
|
363
|
+
eventTypeId: eventTypes[0].id,
|
|
364
|
+
from: new Date(),
|
|
365
|
+
to: nextWeek,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
await client.scheduling.createBooking({
|
|
370
|
+
workspaceId,
|
|
371
|
+
eventTypeId: eventTypes[0].id,
|
|
372
|
+
startAt: slots[0].startAt, // exactly as findSlots gave it
|
|
373
|
+
attendee: { name, phone },
|
|
374
|
+
});
|
|
375
|
+
} catch (error) {
|
|
376
|
+
if (error instanceof ConnectError && error.code === "conflict") {
|
|
377
|
+
// Someone took it between the two calls. Re-read and offer the next one -
|
|
378
|
+
// this is an ordinary outcome on a shared calendar, not a failure.
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
`timezone` is the **business's** zone, not your user's - render the wall time in
|
|
384
|
+
it, or you will offer an hour they are not open.
|
|
385
|
+
|
|
352
386
|
### Attributing a log to a user
|
|
353
387
|
|
|
354
388
|
```ts
|
|
@@ -6,6 +6,7 @@ const apps_js_1 = require("./resources/apps.js");
|
|
|
6
6
|
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
|
+
const scheduling_js_1 = require("./resources/scheduling.js");
|
|
9
10
|
const kv_js_1 = require("./resources/kv.js");
|
|
10
11
|
const billing_js_1 = require("./resources/billing.js");
|
|
11
12
|
const drive_js_1 = require("./resources/drive.js");
|
|
@@ -79,6 +80,8 @@ class ConnectClient {
|
|
|
79
80
|
billing;
|
|
80
81
|
/** Files in a workspace's Drive. */
|
|
81
82
|
drive;
|
|
83
|
+
/** A subscribing workspace's own calendar - what is bookable, free, and booked. */
|
|
84
|
+
scheduling;
|
|
82
85
|
/** RAG chat and structured extraction. */
|
|
83
86
|
ai;
|
|
84
87
|
/** Your application's own document store - see `DataResource`. */
|
|
@@ -127,6 +130,7 @@ class ConnectClient {
|
|
|
127
130
|
this.entitlements = new entitlements_js_1.EntitlementsResource(transport);
|
|
128
131
|
this.billing = new billing_js_1.BillingResource(transport);
|
|
129
132
|
this.drive = new drive_js_1.DriveResource(transport);
|
|
133
|
+
this.scheduling = new scheduling_js_1.SchedulingResource(transport);
|
|
130
134
|
this.ai = new ai_js_1.AiResource(transport);
|
|
131
135
|
this.data = new data_js_1.DataResource(transport);
|
|
132
136
|
this.kv = new kv_js_1.KvResource(transport);
|
|
@@ -142,4 +146,4 @@ class ConnectClient {
|
|
|
142
146
|
}
|
|
143
147
|
exports.ConnectClient = ConnectClient;
|
|
144
148
|
/** Kept in step with `package.json` by `sdk.test.ts`. */
|
|
145
|
-
exports.SDK_VERSION = "0.
|
|
149
|
+
exports.SDK_VERSION = "0.17.0";
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SchedulingResource = void 0;
|
|
4
|
+
const http_js_1 = require("../http.js");
|
|
5
|
+
/**
|
|
6
|
+
* A subscribing workspace's own calendar.
|
|
7
|
+
*
|
|
8
|
+
* **There is one calendar, not one per application.** A booking made here
|
|
9
|
+
* appears on the workspace's own screen immediately, and a slot their
|
|
10
|
+
* receptionist just took is gone from your next `findSlots`. That is the
|
|
11
|
+
* reason to read it through Connect rather than keeping a copy: two calendars
|
|
12
|
+
* double-book, and nothing reconciles them afterwards.
|
|
13
|
+
*
|
|
14
|
+
* Every method needs a live subscription to your application in the workspace
|
|
15
|
+
* you name. The key says which application is calling; the subscription is
|
|
16
|
+
* what makes you welcome in a given tenant.
|
|
17
|
+
*/
|
|
18
|
+
class SchedulingResource {
|
|
19
|
+
transport;
|
|
20
|
+
constructor(transport) {
|
|
21
|
+
this.transport = transport;
|
|
22
|
+
}
|
|
23
|
+
/** What this workspace offers as bookable. Active appointment types only. */
|
|
24
|
+
async listEventTypes(workspaceId) {
|
|
25
|
+
const result = await (0, http_js_1.request)(this.transport, {
|
|
26
|
+
method: "GET",
|
|
27
|
+
path: "/scheduling/event-types",
|
|
28
|
+
query: { workspaceId },
|
|
29
|
+
});
|
|
30
|
+
// `?? []` for the same reason `apps.list` has it - a collection every
|
|
31
|
+
// caller immediately maps, and a body without the key would hand them
|
|
32
|
+
// `undefined` past every status check in between.
|
|
33
|
+
return result.eventTypes ?? [];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Free times for one appointment type over one window.
|
|
37
|
+
*
|
|
38
|
+
* **Advisory by construction.** Between this answer and `createBooking`,
|
|
39
|
+
* anyone else on that calendar can take any of these - a member, a voice
|
|
40
|
+
* agent mid-call, someone on the workspace's public booking link. That is
|
|
41
|
+
* why `createBooking` can answer `409`, and why that is an ordinary outcome
|
|
42
|
+
* rather than a fault.
|
|
43
|
+
*
|
|
44
|
+
* The window is clamped silently to now and to the appointment type's own
|
|
45
|
+
* booking horizon, so asking for a year of a sixty-day type returns sixty
|
|
46
|
+
* days rather than an error.
|
|
47
|
+
*/
|
|
48
|
+
findSlots(input) {
|
|
49
|
+
return (0, http_js_1.request)(this.transport, {
|
|
50
|
+
method: "GET",
|
|
51
|
+
path: "/scheduling/slots",
|
|
52
|
+
query: {
|
|
53
|
+
workspaceId: input.workspaceId,
|
|
54
|
+
eventTypeId: input.eventTypeId,
|
|
55
|
+
from: toInstant(input.from),
|
|
56
|
+
to: toInstant(input.to),
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** One page of the workspace's diary. */
|
|
61
|
+
listBookings(input) {
|
|
62
|
+
return (0, http_js_1.request)(this.transport, {
|
|
63
|
+
method: "GET",
|
|
64
|
+
path: "/scheduling/bookings",
|
|
65
|
+
query: {
|
|
66
|
+
workspaceId: input.workspaceId,
|
|
67
|
+
status: input.status,
|
|
68
|
+
hostMemberId: input.hostMemberId,
|
|
69
|
+
from: input.from ? toInstant(input.from) : undefined,
|
|
70
|
+
to: input.to ? toInstant(input.to) : undefined,
|
|
71
|
+
order: input.order,
|
|
72
|
+
cursor: input.cursor,
|
|
73
|
+
limit: input.limit === undefined ? undefined : String(input.limit),
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Takes one of the times `findSlots` offered.
|
|
79
|
+
*
|
|
80
|
+
* Holds and confirms in one call - you already have the attendee, so there
|
|
81
|
+
* is no half-finished state for your integration to remember. The slot is
|
|
82
|
+
* re-checked inside a transaction, so a time taken since you read it throws
|
|
83
|
+
* a `ConnectError` with `code: "conflict"` rather than double-booking.
|
|
84
|
+
*/
|
|
85
|
+
createBooking(input) {
|
|
86
|
+
return (0, http_js_1.request)(this.transport, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
path: "/scheduling/bookings",
|
|
89
|
+
json: {
|
|
90
|
+
workspaceId: input.workspaceId,
|
|
91
|
+
eventTypeId: input.eventTypeId,
|
|
92
|
+
startAt: toInstant(input.startAt),
|
|
93
|
+
attendee: input.attendee,
|
|
94
|
+
notes: input.notes ?? null,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Frees the slot and records why.
|
|
100
|
+
*
|
|
101
|
+
* Not a deletion: the booking stays on the diary as `cancelled` with the
|
|
102
|
+
* reason, which is what makes a late cancellation something anyone can look
|
|
103
|
+
* up afterwards.
|
|
104
|
+
*/
|
|
105
|
+
cancelBooking(input) {
|
|
106
|
+
return (0, http_js_1.request)(this.transport, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
path: `/scheduling/bookings/${encodeURIComponent(input.bookingId)}/cancel`,
|
|
109
|
+
json: { workspaceId: input.workspaceId, reason: input.reason ?? null },
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.SchedulingResource = SchedulingResource;
|
|
114
|
+
/**
|
|
115
|
+
* A `Date` or an already-ISO string, as the ISO-8601 instant the API takes.
|
|
116
|
+
*
|
|
117
|
+
* `toISOString()` rather than any local formatting: these are instants, and a
|
|
118
|
+
* wall time without a zone is the one input this API cannot interpret.
|
|
119
|
+
*/
|
|
120
|
+
function toInstant(value) {
|
|
121
|
+
return typeof value === "string" ? value : value.toISOString();
|
|
122
|
+
}
|
|
@@ -3,6 +3,7 @@ import { AppsResource } from "./resources/apps.js";
|
|
|
3
3
|
import { AiResource } from "./resources/ai.js";
|
|
4
4
|
import { DataResource, storePassthrough } from "./resources/data.js";
|
|
5
5
|
import { WorkspacesResource } from "./resources/workspaces.js";
|
|
6
|
+
import { SchedulingResource } from "./resources/scheduling.js";
|
|
6
7
|
import { KvResource } from "./resources/kv.js";
|
|
7
8
|
import { BillingResource } from "./resources/billing.js";
|
|
8
9
|
import { DriveResource } from "./resources/drive.js";
|
|
@@ -76,6 +77,8 @@ export class ConnectClient {
|
|
|
76
77
|
billing;
|
|
77
78
|
/** Files in a workspace's Drive. */
|
|
78
79
|
drive;
|
|
80
|
+
/** A subscribing workspace's own calendar - what is bookable, free, and booked. */
|
|
81
|
+
scheduling;
|
|
79
82
|
/** RAG chat and structured extraction. */
|
|
80
83
|
ai;
|
|
81
84
|
/** Your application's own document store - see `DataResource`. */
|
|
@@ -124,6 +127,7 @@ export class ConnectClient {
|
|
|
124
127
|
this.entitlements = new EntitlementsResource(transport);
|
|
125
128
|
this.billing = new BillingResource(transport);
|
|
126
129
|
this.drive = new DriveResource(transport);
|
|
130
|
+
this.scheduling = new SchedulingResource(transport);
|
|
127
131
|
this.ai = new AiResource(transport);
|
|
128
132
|
this.data = new DataResource(transport);
|
|
129
133
|
this.kv = new KvResource(transport);
|
|
@@ -138,4 +142,4 @@ export class ConnectClient {
|
|
|
138
142
|
}
|
|
139
143
|
}
|
|
140
144
|
/** Kept in step with `package.json` by `sdk.test.ts`. */
|
|
141
|
-
export const SDK_VERSION = "0.
|
|
145
|
+
export const SDK_VERSION = "0.17.0";
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { request } from "../http.js";
|
|
2
|
+
/**
|
|
3
|
+
* A subscribing workspace's own calendar.
|
|
4
|
+
*
|
|
5
|
+
* **There is one calendar, not one per application.** A booking made here
|
|
6
|
+
* appears on the workspace's own screen immediately, and a slot their
|
|
7
|
+
* receptionist just took is gone from your next `findSlots`. That is the
|
|
8
|
+
* reason to read it through Connect rather than keeping a copy: two calendars
|
|
9
|
+
* double-book, and nothing reconciles them afterwards.
|
|
10
|
+
*
|
|
11
|
+
* Every method needs a live subscription to your application in the workspace
|
|
12
|
+
* you name. The key says which application is calling; the subscription is
|
|
13
|
+
* what makes you welcome in a given tenant.
|
|
14
|
+
*/
|
|
15
|
+
export class SchedulingResource {
|
|
16
|
+
transport;
|
|
17
|
+
constructor(transport) {
|
|
18
|
+
this.transport = transport;
|
|
19
|
+
}
|
|
20
|
+
/** What this workspace offers as bookable. Active appointment types only. */
|
|
21
|
+
async listEventTypes(workspaceId) {
|
|
22
|
+
const result = await request(this.transport, {
|
|
23
|
+
method: "GET",
|
|
24
|
+
path: "/scheduling/event-types",
|
|
25
|
+
query: { workspaceId },
|
|
26
|
+
});
|
|
27
|
+
// `?? []` for the same reason `apps.list` has it - a collection every
|
|
28
|
+
// caller immediately maps, and a body without the key would hand them
|
|
29
|
+
// `undefined` past every status check in between.
|
|
30
|
+
return result.eventTypes ?? [];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Free times for one appointment type over one window.
|
|
34
|
+
*
|
|
35
|
+
* **Advisory by construction.** Between this answer and `createBooking`,
|
|
36
|
+
* anyone else on that calendar can take any of these - a member, a voice
|
|
37
|
+
* agent mid-call, someone on the workspace's public booking link. That is
|
|
38
|
+
* why `createBooking` can answer `409`, and why that is an ordinary outcome
|
|
39
|
+
* rather than a fault.
|
|
40
|
+
*
|
|
41
|
+
* The window is clamped silently to now and to the appointment type's own
|
|
42
|
+
* booking horizon, so asking for a year of a sixty-day type returns sixty
|
|
43
|
+
* days rather than an error.
|
|
44
|
+
*/
|
|
45
|
+
findSlots(input) {
|
|
46
|
+
return request(this.transport, {
|
|
47
|
+
method: "GET",
|
|
48
|
+
path: "/scheduling/slots",
|
|
49
|
+
query: {
|
|
50
|
+
workspaceId: input.workspaceId,
|
|
51
|
+
eventTypeId: input.eventTypeId,
|
|
52
|
+
from: toInstant(input.from),
|
|
53
|
+
to: toInstant(input.to),
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** One page of the workspace's diary. */
|
|
58
|
+
listBookings(input) {
|
|
59
|
+
return request(this.transport, {
|
|
60
|
+
method: "GET",
|
|
61
|
+
path: "/scheduling/bookings",
|
|
62
|
+
query: {
|
|
63
|
+
workspaceId: input.workspaceId,
|
|
64
|
+
status: input.status,
|
|
65
|
+
hostMemberId: input.hostMemberId,
|
|
66
|
+
from: input.from ? toInstant(input.from) : undefined,
|
|
67
|
+
to: input.to ? toInstant(input.to) : undefined,
|
|
68
|
+
order: input.order,
|
|
69
|
+
cursor: input.cursor,
|
|
70
|
+
limit: input.limit === undefined ? undefined : String(input.limit),
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Takes one of the times `findSlots` offered.
|
|
76
|
+
*
|
|
77
|
+
* Holds and confirms in one call - you already have the attendee, so there
|
|
78
|
+
* is no half-finished state for your integration to remember. The slot is
|
|
79
|
+
* re-checked inside a transaction, so a time taken since you read it throws
|
|
80
|
+
* a `ConnectError` with `code: "conflict"` rather than double-booking.
|
|
81
|
+
*/
|
|
82
|
+
createBooking(input) {
|
|
83
|
+
return request(this.transport, {
|
|
84
|
+
method: "POST",
|
|
85
|
+
path: "/scheduling/bookings",
|
|
86
|
+
json: {
|
|
87
|
+
workspaceId: input.workspaceId,
|
|
88
|
+
eventTypeId: input.eventTypeId,
|
|
89
|
+
startAt: toInstant(input.startAt),
|
|
90
|
+
attendee: input.attendee,
|
|
91
|
+
notes: input.notes ?? null,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Frees the slot and records why.
|
|
97
|
+
*
|
|
98
|
+
* Not a deletion: the booking stays on the diary as `cancelled` with the
|
|
99
|
+
* reason, which is what makes a late cancellation something anyone can look
|
|
100
|
+
* up afterwards.
|
|
101
|
+
*/
|
|
102
|
+
cancelBooking(input) {
|
|
103
|
+
return request(this.transport, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
path: `/scheduling/bookings/${encodeURIComponent(input.bookingId)}/cancel`,
|
|
106
|
+
json: { workspaceId: input.workspaceId, reason: input.reason ?? null },
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* A `Date` or an already-ISO string, as the ISO-8601 instant the API takes.
|
|
112
|
+
*
|
|
113
|
+
* `toISOString()` rather than any local formatting: these are instants, and a
|
|
114
|
+
* wall time without a zone is the one input this API cannot interpret.
|
|
115
|
+
*/
|
|
116
|
+
function toInstant(value) {
|
|
117
|
+
return typeof value === "string" ? value : value.toISOString();
|
|
118
|
+
}
|
|
@@ -55,14 +55,25 @@ export interface McpToolDescriptor {
|
|
|
55
55
|
idempotentHint: boolean;
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
|
-
/**
|
|
59
|
-
|
|
58
|
+
/**
|
|
59
|
+
* An MCP `tools/call` result.
|
|
60
|
+
*
|
|
61
|
+
* A **type alias rather than an interface**, and that is load-bearing rather
|
|
62
|
+
* than a style choice. The MCP SDK types a request handler's return as a union
|
|
63
|
+
* whose members carry an index signature (`[x: string]: unknown`); an interface
|
|
64
|
+
* is not assignable to such a type, while a type alias is - TypeScript grants
|
|
65
|
+
* the implicit index signature only to the latter. Declared as an interface,
|
|
66
|
+
* the four-line wiring in this file's own example does not compile, and the
|
|
67
|
+
* caller is pushed into a cast at exactly the boundary where a cast hides
|
|
68
|
+
* shape errors.
|
|
69
|
+
*/
|
|
70
|
+
export type McpToolResult = {
|
|
60
71
|
content: {
|
|
61
72
|
type: "text";
|
|
62
73
|
text: string;
|
|
63
74
|
}[];
|
|
64
75
|
isError?: boolean;
|
|
65
|
-
}
|
|
76
|
+
};
|
|
66
77
|
export interface StoreToolset {
|
|
67
78
|
/** The derived tools, if you would rather register them yourself. */
|
|
68
79
|
readonly tools: readonly StoreTool[];
|
|
@@ -4,6 +4,7 @@ import { AppsResource } from "./resources/apps.js";
|
|
|
4
4
|
import { AiResource } from "./resources/ai.js";
|
|
5
5
|
import { DataResource } from "./resources/data.js";
|
|
6
6
|
import { WorkspacesResource } from "./resources/workspaces.js";
|
|
7
|
+
import { SchedulingResource } from "./resources/scheduling.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";
|
|
@@ -75,6 +76,8 @@ export declare class ConnectClient {
|
|
|
75
76
|
readonly billing: BillingResource;
|
|
76
77
|
/** Files in a workspace's Drive. */
|
|
77
78
|
readonly drive: DriveResource;
|
|
79
|
+
/** A subscribing workspace's own calendar - what is bookable, free, and booked. */
|
|
80
|
+
readonly scheduling: SchedulingResource;
|
|
78
81
|
/** RAG chat and structured extraction. */
|
|
79
82
|
readonly ai: AiResource;
|
|
80
83
|
/** Your application's own document store - see `DataResource`. */
|
|
@@ -109,4 +112,4 @@ export declare class ConnectClient {
|
|
|
109
112
|
constructor(options: ConnectClientOptions);
|
|
110
113
|
}
|
|
111
114
|
/** Kept in step with `package.json` by `sdk.test.ts`. */
|
|
112
|
-
export declare const SDK_VERSION = "0.
|
|
115
|
+
export declare const SDK_VERSION = "0.17.0";
|
|
@@ -27,6 +27,7 @@ export type { AnalyticsResource } from "./resources/analytics.js";
|
|
|
27
27
|
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
|
+
export type { SchedulingResource, FindSlotsInput, ListBookingsInput, CreateBookingInput, } from "./resources/scheduling.js";
|
|
30
31
|
/**
|
|
31
32
|
* Webhook verification and a ready-made POST route. Server-only for the
|
|
32
33
|
* obvious reason: the signing secret must never reach a browser bundle.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { type TransportOptions } from "../http.js";
|
|
2
|
+
import type { BookableEventType, Booking, BookingAttendee, BookingSlots, BookingStatus, ListBookingsPage } from "../../types.js";
|
|
3
|
+
export interface FindSlotsInput {
|
|
4
|
+
workspaceId: string;
|
|
5
|
+
eventTypeId: string;
|
|
6
|
+
/** ISO-8601 instant, or a `Date`. */
|
|
7
|
+
from: string | Date;
|
|
8
|
+
to: string | Date;
|
|
9
|
+
}
|
|
10
|
+
export interface ListBookingsInput {
|
|
11
|
+
workspaceId: string;
|
|
12
|
+
status?: BookingStatus;
|
|
13
|
+
/** A Connect workspace member id - only bookings that committed them. */
|
|
14
|
+
hostMemberId?: string;
|
|
15
|
+
from?: string | Date;
|
|
16
|
+
to?: string | Date;
|
|
17
|
+
/** `asc` for a forward diary, `desc` (the default) for a history. */
|
|
18
|
+
order?: "asc" | "desc";
|
|
19
|
+
cursor?: string;
|
|
20
|
+
limit?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface CreateBookingInput {
|
|
23
|
+
workspaceId: string;
|
|
24
|
+
eventTypeId: string;
|
|
25
|
+
/**
|
|
26
|
+
* The `startAt` from a `findSlots` result, passed back unchanged.
|
|
27
|
+
*
|
|
28
|
+
* Re-deriving it from a local wall time is how an integration books an hour
|
|
29
|
+
* that was never offered - the slot list is computed in the *business's*
|
|
30
|
+
* timezone, which is rarely the one your process is running in.
|
|
31
|
+
*/
|
|
32
|
+
startAt: string | Date;
|
|
33
|
+
attendee: BookingAttendee;
|
|
34
|
+
notes?: string | null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A subscribing workspace's own calendar.
|
|
38
|
+
*
|
|
39
|
+
* **There is one calendar, not one per application.** A booking made here
|
|
40
|
+
* appears on the workspace's own screen immediately, and a slot their
|
|
41
|
+
* receptionist just took is gone from your next `findSlots`. That is the
|
|
42
|
+
* reason to read it through Connect rather than keeping a copy: two calendars
|
|
43
|
+
* double-book, and nothing reconciles them afterwards.
|
|
44
|
+
*
|
|
45
|
+
* Every method needs a live subscription to your application in the workspace
|
|
46
|
+
* you name. The key says which application is calling; the subscription is
|
|
47
|
+
* what makes you welcome in a given tenant.
|
|
48
|
+
*/
|
|
49
|
+
export declare class SchedulingResource {
|
|
50
|
+
private readonly transport;
|
|
51
|
+
constructor(transport: TransportOptions);
|
|
52
|
+
/** What this workspace offers as bookable. Active appointment types only. */
|
|
53
|
+
listEventTypes(workspaceId: string): Promise<BookableEventType[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Free times for one appointment type over one window.
|
|
56
|
+
*
|
|
57
|
+
* **Advisory by construction.** Between this answer and `createBooking`,
|
|
58
|
+
* anyone else on that calendar can take any of these - a member, a voice
|
|
59
|
+
* agent mid-call, someone on the workspace's public booking link. That is
|
|
60
|
+
* why `createBooking` can answer `409`, and why that is an ordinary outcome
|
|
61
|
+
* rather than a fault.
|
|
62
|
+
*
|
|
63
|
+
* The window is clamped silently to now and to the appointment type's own
|
|
64
|
+
* booking horizon, so asking for a year of a sixty-day type returns sixty
|
|
65
|
+
* days rather than an error.
|
|
66
|
+
*/
|
|
67
|
+
findSlots(input: FindSlotsInput): Promise<BookingSlots>;
|
|
68
|
+
/** One page of the workspace's diary. */
|
|
69
|
+
listBookings(input: ListBookingsInput): Promise<ListBookingsPage>;
|
|
70
|
+
/**
|
|
71
|
+
* Takes one of the times `findSlots` offered.
|
|
72
|
+
*
|
|
73
|
+
* Holds and confirms in one call - you already have the attendee, so there
|
|
74
|
+
* is no half-finished state for your integration to remember. The slot is
|
|
75
|
+
* re-checked inside a transaction, so a time taken since you read it throws
|
|
76
|
+
* a `ConnectError` with `code: "conflict"` rather than double-booking.
|
|
77
|
+
*/
|
|
78
|
+
createBooking(input: CreateBookingInput): Promise<Booking>;
|
|
79
|
+
/**
|
|
80
|
+
* Frees the slot and records why.
|
|
81
|
+
*
|
|
82
|
+
* Not a deletion: the booking stays on the diary as `cancelled` with the
|
|
83
|
+
* reason, which is what makes a late cancellation something anyone can look
|
|
84
|
+
* up afterwards.
|
|
85
|
+
*/
|
|
86
|
+
cancelBooking(input: {
|
|
87
|
+
workspaceId: string;
|
|
88
|
+
bookingId: string;
|
|
89
|
+
reason?: string | null;
|
|
90
|
+
}): Promise<Booking>;
|
|
91
|
+
}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1140,3 +1140,74 @@ export interface ConnectWorkspaceMember {
|
|
|
1140
1140
|
granted: string[];
|
|
1141
1141
|
role: string | null;
|
|
1142
1142
|
}
|
|
1143
|
+
/**
|
|
1144
|
+
* One bookable thing a workspace offers - "15-minute intro call", "site
|
|
1145
|
+
* survey".
|
|
1146
|
+
*
|
|
1147
|
+
* `locationKind` and never the location itself: the address or the meeting
|
|
1148
|
+
* link belongs to whoever booked, and this is a catalogue.
|
|
1149
|
+
*/
|
|
1150
|
+
export interface BookableEventType {
|
|
1151
|
+
id: string;
|
|
1152
|
+
/** Stable and readable, unique within the workspace - branch on this, not `id`. */
|
|
1153
|
+
slug: string;
|
|
1154
|
+
name: string;
|
|
1155
|
+
description: string | null;
|
|
1156
|
+
durationMinutes: number;
|
|
1157
|
+
locationKind: "phone" | "address" | "link";
|
|
1158
|
+
}
|
|
1159
|
+
export interface BookingSlot {
|
|
1160
|
+
/** ISO-8601 instant. Pass this back to `createBooking` unchanged. */
|
|
1161
|
+
startAt: string;
|
|
1162
|
+
endAt: string;
|
|
1163
|
+
}
|
|
1164
|
+
export interface BookingSlots {
|
|
1165
|
+
/**
|
|
1166
|
+
* The **business's** IANA timezone, not yours and not your user's - what to
|
|
1167
|
+
* render a slot's wall time in. A slot shown in the viewer's zone is an hour
|
|
1168
|
+
* the business may not be open.
|
|
1169
|
+
*/
|
|
1170
|
+
timezone: string;
|
|
1171
|
+
durationMinutes: number;
|
|
1172
|
+
slots: BookingSlot[];
|
|
1173
|
+
}
|
|
1174
|
+
export interface BookingAttendee {
|
|
1175
|
+
name: string;
|
|
1176
|
+
phone: string;
|
|
1177
|
+
email?: string | null;
|
|
1178
|
+
}
|
|
1179
|
+
export type BookingStatus = "hold" | "confirmed" | "cancelled" | "rescheduled" | "no_show";
|
|
1180
|
+
/**
|
|
1181
|
+
* Where a booking came from - usually the first question anyone asks about an
|
|
1182
|
+
* appointment they did not expect.
|
|
1183
|
+
*
|
|
1184
|
+
* `api` is your integration; `portal` is the workspace's own screen or its
|
|
1185
|
+
* public booking link; `agent-call` is a voice agent, mid-call.
|
|
1186
|
+
*/
|
|
1187
|
+
export type BookingSource = "agent-call" | "portal" | "api";
|
|
1188
|
+
export interface Booking {
|
|
1189
|
+
id: string;
|
|
1190
|
+
eventTypeId: string;
|
|
1191
|
+
status: BookingStatus;
|
|
1192
|
+
/** ISO-8601 instants. */
|
|
1193
|
+
startAt: string;
|
|
1194
|
+
endAt: string;
|
|
1195
|
+
/** The zone the two parties agreed the time in, frozen at booking. */
|
|
1196
|
+
timezone: string;
|
|
1197
|
+
attendee: {
|
|
1198
|
+
name: string;
|
|
1199
|
+
phone: string;
|
|
1200
|
+
email: string | null;
|
|
1201
|
+
};
|
|
1202
|
+
notes: string | null;
|
|
1203
|
+
source: BookingSource;
|
|
1204
|
+
cancelledReason: string | null;
|
|
1205
|
+
createdAt: string;
|
|
1206
|
+
}
|
|
1207
|
+
export interface ListBookingsPage {
|
|
1208
|
+
bookings: Booking[];
|
|
1209
|
+
/** Pass back as `cursor`. A cursor belongs to one set of filters. */
|
|
1210
|
+
nextCursor: string | null;
|
|
1211
|
+
hasMore: boolean;
|
|
1212
|
+
totalCount: number;
|
|
1213
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmoops/connect-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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",
|