openclaw-ringcentral 2026.1.29-beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/api.ts ADDED
@@ -0,0 +1,241 @@
1
+ import type { ResolvedRingCentralAccount } from "./accounts.js";
2
+ import { getRingCentralPlatform } from "./auth.js";
3
+ import { toRingCentralMarkdown } from "./markdown.js";
4
+ import type {
5
+ RingCentralChat,
6
+ RingCentralPost,
7
+ RingCentralUser,
8
+ RingCentralAttachment,
9
+ RingCentralAdaptiveCard,
10
+ } from "./types.js";
11
+
12
+ // Team Messaging API endpoints
13
+ const TM_API_BASE = "/team-messaging/v1";
14
+
15
+ export async function sendRingCentralMessage(params: {
16
+ account: ResolvedRingCentralAccount;
17
+ chatId: string;
18
+ text?: string;
19
+ attachments?: Array<{ id: string; type?: string }>;
20
+ }): Promise<{ postId?: string } | null> {
21
+ const { account, chatId, text, attachments } = params;
22
+ const platform = await getRingCentralPlatform(account);
23
+
24
+ const body: Record<string, unknown> = {};
25
+ if (text) body.text = toRingCentralMarkdown(text);
26
+ if (attachments && attachments.length > 0) {
27
+ body.attachments = attachments;
28
+ }
29
+
30
+ const response = await platform.post(`${TM_API_BASE}/chats/${chatId}/posts`, body);
31
+ const result = (await response.json()) as RingCentralPost;
32
+ return result ? { postId: result.id } : null;
33
+ }
34
+
35
+ export async function updateRingCentralMessage(params: {
36
+ account: ResolvedRingCentralAccount;
37
+ chatId: string;
38
+ postId: string;
39
+ text: string;
40
+ }): Promise<{ postId?: string }> {
41
+ const { account, chatId, postId, text } = params;
42
+ const platform = await getRingCentralPlatform(account);
43
+
44
+ const response = await platform.patch(
45
+ `${TM_API_BASE}/chats/${chatId}/posts/${postId}`,
46
+ { text: toRingCentralMarkdown(text) },
47
+ );
48
+ const result = (await response.json()) as RingCentralPost;
49
+ return { postId: result.id };
50
+ }
51
+
52
+ export async function deleteRingCentralMessage(params: {
53
+ account: ResolvedRingCentralAccount;
54
+ chatId: string;
55
+ postId: string;
56
+ }): Promise<void> {
57
+ const { account, chatId, postId } = params;
58
+ const platform = await getRingCentralPlatform(account);
59
+ await platform.delete(`${TM_API_BASE}/chats/${chatId}/posts/${postId}`);
60
+ }
61
+
62
+ export async function getRingCentralChat(params: {
63
+ account: ResolvedRingCentralAccount;
64
+ chatId: string;
65
+ }): Promise<RingCentralChat | null> {
66
+ const { account, chatId } = params;
67
+ const platform = await getRingCentralPlatform(account);
68
+
69
+ try {
70
+ const response = await platform.get(`${TM_API_BASE}/chats/${chatId}`);
71
+ return (await response.json()) as RingCentralChat;
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+
77
+ export async function listRingCentralChats(params: {
78
+ account: ResolvedRingCentralAccount;
79
+ type?: string[];
80
+ limit?: number;
81
+ }): Promise<RingCentralChat[]> {
82
+ const { account, type, limit } = params;
83
+ const platform = await getRingCentralPlatform(account);
84
+
85
+ const queryParams: Record<string, string> = {};
86
+ if (type && type.length > 0) queryParams.type = type.join(",");
87
+ if (limit) queryParams.recordCount = String(limit);
88
+
89
+ const response = await platform.get(`${TM_API_BASE}/chats`, queryParams);
90
+ const result = (await response.json()) as { records?: RingCentralChat[] };
91
+ return result.records ?? [];
92
+ }
93
+
94
+ export async function getRingCentralUser(params: {
95
+ account: ResolvedRingCentralAccount;
96
+ userId: string;
97
+ }): Promise<RingCentralUser | null> {
98
+ const { account, userId } = params;
99
+ const platform = await getRingCentralPlatform(account);
100
+
101
+ try {
102
+ const response = await platform.get(`${TM_API_BASE}/persons/${userId}`);
103
+ return (await response.json()) as RingCentralUser;
104
+ } catch {
105
+ return null;
106
+ }
107
+ }
108
+
109
+ export async function getCurrentRingCentralUser(params: {
110
+ account: ResolvedRingCentralAccount;
111
+ }): Promise<RingCentralUser | null> {
112
+ const { account } = params;
113
+ const platform = await getRingCentralPlatform(account);
114
+
115
+ try {
116
+ const response = await platform.get("/restapi/v1.0/account/~/extension/~");
117
+ return (await response.json()) as RingCentralUser;
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+
123
+ export async function uploadRingCentralAttachment(params: {
124
+ account: ResolvedRingCentralAccount;
125
+ chatId: string;
126
+ filename: string;
127
+ buffer: Buffer;
128
+ contentType?: string;
129
+ }): Promise<{ attachmentId?: string }> {
130
+ const { account, chatId, filename, buffer, contentType } = params;
131
+ const platform = await getRingCentralPlatform(account);
132
+
133
+ // Create FormData for multipart upload
134
+ const formData = new FormData();
135
+ const blob = new Blob([buffer], { type: contentType || "application/octet-stream" });
136
+ formData.append("file", blob, filename);
137
+
138
+ const response = await platform.post(
139
+ `${TM_API_BASE}/chats/${chatId}/files`,
140
+ formData,
141
+ );
142
+ const result = (await response.json()) as RingCentralAttachment;
143
+ return { attachmentId: result.id };
144
+ }
145
+
146
+ export async function downloadRingCentralAttachment(params: {
147
+ account: ResolvedRingCentralAccount;
148
+ contentUri: string;
149
+ maxBytes?: number;
150
+ }): Promise<{ buffer: Buffer; contentType?: string }> {
151
+ const { account, contentUri, maxBytes } = params;
152
+ const platform = await getRingCentralPlatform(account);
153
+
154
+ const response = await platform.get(contentUri);
155
+ const contentType = response.headers.get("content-type") ?? undefined;
156
+ const arrayBuffer = await response.arrayBuffer();
157
+
158
+ if (maxBytes && arrayBuffer.byteLength > maxBytes) {
159
+ throw new Error(`RingCentral attachment exceeds max bytes (${maxBytes})`);
160
+ }
161
+
162
+ return {
163
+ buffer: Buffer.from(arrayBuffer),
164
+ contentType,
165
+ };
166
+ }
167
+
168
+ // Adaptive Cards API
169
+ export async function sendRingCentralAdaptiveCard(params: {
170
+ account: ResolvedRingCentralAccount;
171
+ chatId: string;
172
+ card: RingCentralAdaptiveCard;
173
+ fallbackText?: string;
174
+ }): Promise<{ cardId?: string } | null> {
175
+ const { account, chatId, card, fallbackText } = params;
176
+ const platform = await getRingCentralPlatform(account);
177
+
178
+ const body = {
179
+ ...card,
180
+ type: "AdaptiveCard",
181
+ $schema: card.$schema ?? "http://adaptivecards.io/schemas/adaptive-card.json",
182
+ version: card.version ?? "1.3",
183
+ ...(fallbackText ? { fallbackText } : {}),
184
+ };
185
+
186
+ const response = await platform.post(`${TM_API_BASE}/chats/${chatId}/adaptive-cards`, body);
187
+ const result = (await response.json()) as { id?: string };
188
+ return result ? { cardId: result.id } : null;
189
+ }
190
+
191
+ export async function updateRingCentralAdaptiveCard(params: {
192
+ account: ResolvedRingCentralAccount;
193
+ cardId: string;
194
+ card: RingCentralAdaptiveCard;
195
+ fallbackText?: string;
196
+ }): Promise<{ cardId?: string }> {
197
+ const { account, cardId, card, fallbackText } = params;
198
+ const platform = await getRingCentralPlatform(account);
199
+
200
+ const body = {
201
+ ...card,
202
+ type: "AdaptiveCard",
203
+ $schema: card.$schema ?? "http://adaptivecards.io/schemas/adaptive-card.json",
204
+ version: card.version ?? "1.3",
205
+ ...(fallbackText ? { fallbackText } : {}),
206
+ };
207
+
208
+ const response = await platform.put(`${TM_API_BASE}/adaptive-cards/${cardId}`, body);
209
+ const result = (await response.json()) as { id?: string };
210
+ return { cardId: result.id };
211
+ }
212
+
213
+ export async function deleteRingCentralAdaptiveCard(params: {
214
+ account: ResolvedRingCentralAccount;
215
+ cardId: string;
216
+ }): Promise<void> {
217
+ const { account, cardId } = params;
218
+ const platform = await getRingCentralPlatform(account);
219
+ await platform.delete(`${TM_API_BASE}/adaptive-cards/${cardId}`);
220
+ }
221
+
222
+ export async function probeRingCentral(
223
+ account: ResolvedRingCentralAccount,
224
+ ): Promise<{ ok: boolean; error?: string; elapsedMs: number }> {
225
+ const start = Date.now();
226
+ try {
227
+ const user = await getCurrentRingCentralUser({ account });
228
+ const elapsedMs = Date.now() - start;
229
+ if (user?.id) {
230
+ return { ok: true, elapsedMs };
231
+ }
232
+ return { ok: false, error: "Unable to fetch current user", elapsedMs };
233
+ } catch (err) {
234
+ const elapsedMs = Date.now() - start;
235
+ return {
236
+ ok: false,
237
+ error: err instanceof Error ? err.message : String(err),
238
+ elapsedMs,
239
+ };
240
+ }
241
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,92 @@
1
+ import { SDK } from "@ringcentral/sdk";
2
+
3
+ import type { ResolvedRingCentralAccount } from "./accounts.js";
4
+
5
+ export type SDKInstance = InstanceType<typeof SDK>;
6
+
7
+ const sdkCache = new Map<string, { key: string; sdk: SDKInstance; platform: ReturnType<SDKInstance["platform"]> }>();
8
+
9
+ function buildAuthKey(account: ResolvedRingCentralAccount): string {
10
+ return `${account.clientId}:${account.server}:${account.jwt?.slice(0, 20)}`;
11
+ }
12
+
13
+ async function getSDKInstance(account: ResolvedRingCentralAccount): Promise<{
14
+ sdk: SDKInstance;
15
+ platform: ReturnType<SDKInstance["platform"]>;
16
+ }> {
17
+ const key = buildAuthKey(account);
18
+ const cached = sdkCache.get(account.accountId);
19
+
20
+ if (cached && cached.key === key) {
21
+ // Check if still logged in
22
+ const platform = cached.platform;
23
+ try {
24
+ const loggedIn = await platform.loggedIn();
25
+ if (loggedIn) {
26
+ return cached;
27
+ }
28
+ } catch {
29
+ // Token expired or invalid, need to re-login
30
+ }
31
+ }
32
+
33
+ if (!account.clientId || !account.clientSecret) {
34
+ throw new Error("RingCentral clientId and clientSecret are required");
35
+ }
36
+
37
+ if (!account.jwt) {
38
+ throw new Error("RingCentral JWT token is required for authentication");
39
+ }
40
+
41
+ const sdk = new SDK({
42
+ server: account.server,
43
+ clientId: account.clientId,
44
+ clientSecret: account.clientSecret,
45
+ });
46
+
47
+ const platform = sdk.platform();
48
+
49
+ // Login using JWT
50
+ await platform.login({ jwt: account.jwt });
51
+
52
+ const entry = { key, sdk, platform };
53
+ sdkCache.set(account.accountId, entry);
54
+ return entry;
55
+ }
56
+
57
+ export async function getRingCentralSDK(
58
+ account: ResolvedRingCentralAccount,
59
+ ): Promise<SDKInstance> {
60
+ const { sdk } = await getSDKInstance(account);
61
+ return sdk;
62
+ }
63
+
64
+ export async function getRingCentralPlatform(
65
+ account: ResolvedRingCentralAccount,
66
+ ): Promise<ReturnType<SDKInstance["platform"]>> {
67
+ const { platform } = await getSDKInstance(account);
68
+ return platform;
69
+ }
70
+
71
+ export async function getRingCentralAccessToken(
72
+ account: ResolvedRingCentralAccount,
73
+ ): Promise<string> {
74
+ const { platform } = await getSDKInstance(account);
75
+ const authData = await platform.auth().data();
76
+ const token = authData?.access_token;
77
+ if (!token) {
78
+ throw new Error("Missing RingCentral access token");
79
+ }
80
+ return token;
81
+ }
82
+
83
+ export async function refreshRingCentralToken(
84
+ account: ResolvedRingCentralAccount,
85
+ ): Promise<void> {
86
+ const { platform } = await getSDKInstance(account);
87
+ await platform.refresh();
88
+ }
89
+
90
+ export function clearRingCentralAuth(accountId: string): void {
91
+ sdkCache.delete(accountId);
92
+ }