@superblocksteam/sdk 1.4.2 → 1.5.1

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.
@@ -0,0 +1,253 @@
1
+ import WebSocket from "isomorphic-ws";
2
+ import {
3
+ ISocketClient,
4
+ MethodHandler,
5
+ MethodHandlers,
6
+ MiddlewareHandler,
7
+ } from "../types";
8
+
9
+ interface SocketRequest<Payload = unknown> {
10
+ method: string;
11
+ payload: Payload;
12
+ id: number;
13
+ setAuthorization?: string;
14
+ }
15
+
16
+ interface SocketResponse<Payload = unknown> {
17
+ id: number;
18
+ payload: Payload;
19
+ error: SocketError | null;
20
+ }
21
+
22
+ interface SocketMessage<RequestPayload = unknown, ResponsePayload = unknown> {
23
+ request?: SocketRequest<RequestPayload>;
24
+ response?: SocketResponse<ResponsePayload>;
25
+ }
26
+
27
+ interface SocketError {
28
+ message: string;
29
+ code: number;
30
+ }
31
+
32
+ export class ISocket<ImplementedMethods, CallableMethods, RequestContext> {
33
+ private readonly ws: WebSocket;
34
+ private readonly requestHandlers: MethodHandlers<
35
+ ImplementedMethods,
36
+ CallableMethods,
37
+ RequestContext
38
+ >;
39
+ private readonly responseHandler: {
40
+ [requestId: number]: {
41
+ resolve: (data: unknown) => void;
42
+ reject: (error: SocketError) => void;
43
+ };
44
+ } = {};
45
+ private peerAuthorization?: string;
46
+ private nxtRequestId: number;
47
+
48
+ constructor(
49
+ ws: WebSocket,
50
+ requestHandlers: MethodHandlers<
51
+ ImplementedMethods,
52
+ CallableMethods,
53
+ RequestContext
54
+ >
55
+ ) {
56
+ this.ws = ws;
57
+ this.requestHandlers = requestHandlers;
58
+ this.nxtRequestId = 0;
59
+
60
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
61
+ // @ts-ignore
62
+ this.ws.addEventListener("message", async (event: MessageEvent) => {
63
+ const eventData: SocketMessage = JSON.parse(event.data.toString());
64
+ if (eventData.request) {
65
+ // Split the method string into parts
66
+ const parts = eventData.request.method.split(".");
67
+ let handlers = this.requestHandlers;
68
+ for (const part of parts) {
69
+ // @ts-ignore
70
+ handlers = handlers[part];
71
+ if (!handlers) {
72
+ return await this.respondError(eventData.request.id, {
73
+ code: 2,
74
+ message: `unknown method ${eventData.request.method}`,
75
+ });
76
+ }
77
+ }
78
+
79
+ if (!Array.isArray(handlers)) {
80
+ return await this.respondError(eventData.request.id, {
81
+ code: 2,
82
+ message: "unknown method",
83
+ });
84
+ }
85
+ if (eventData.request.setAuthorization) {
86
+ this.peerAuthorization = eventData.request.setAuthorization;
87
+ }
88
+ const middlewareHandlers = handlers.slice(0, -1) as MiddlewareHandler<
89
+ unknown,
90
+ CallableMethods,
91
+ RequestContext
92
+ >[];
93
+ const handler = handlers[handlers.length - 1] as MethodHandler<
94
+ unknown,
95
+ unknown,
96
+ CallableMethods,
97
+ RequestContext
98
+ >;
99
+ const reqCtx = {} as RequestContext;
100
+ let response: unknown;
101
+ // TODO(george): maybe we should not create a new client for each request
102
+ const client = createISocketClient(this);
103
+ try {
104
+ for (const middlewareHandler of middlewareHandlers) {
105
+ await middlewareHandler(
106
+ eventData.request.payload,
107
+ this.peerAuthorization,
108
+ client,
109
+ reqCtx
110
+ );
111
+ }
112
+ response = await handler(eventData.request.payload, client, reqCtx);
113
+ } catch (error: any) {
114
+ return await this.respondError(eventData.request.id, {
115
+ code: 3,
116
+ message: error.toString(),
117
+ });
118
+ }
119
+ await this.respond(eventData.request.id, response);
120
+ } else if (eventData.response && eventData.response.id) {
121
+ if (!this.responseHandler[eventData.response.id]) {
122
+ return;
123
+ }
124
+ if (eventData.response.error) {
125
+ this.responseHandler[eventData.response.id].reject(
126
+ eventData.response.error
127
+ );
128
+ }
129
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
130
+ this.responseHandler[eventData.response.id].resolve(
131
+ eventData.response.payload as any
132
+ );
133
+ delete this.responseHandler[eventData.response.id];
134
+ } else {
135
+ return await this.respondError(-1, {
136
+ code: 3,
137
+ message: "unknown request id",
138
+ });
139
+ }
140
+ });
141
+ }
142
+
143
+ public request<Params, Result>(
144
+ method: string,
145
+ params: Params,
146
+ authorization?: string
147
+ ): Promise<Result> {
148
+ return new Promise<Result>((resolve, reject) => {
149
+ const requestId = ++this.nxtRequestId;
150
+ this.responseHandler[requestId] = {
151
+ resolve: (result) => resolve(result as Result),
152
+ reject: (error: SocketError) => reject(error),
153
+ };
154
+ const toSend: SocketMessage = {
155
+ request: {
156
+ method,
157
+ payload: params,
158
+ id: requestId,
159
+ setAuthorization: authorization,
160
+ },
161
+ };
162
+ this.ws.send(JSON.stringify(toSend));
163
+ });
164
+ }
165
+
166
+ private async respond<Result>(
167
+ requestId: number,
168
+ result: Result
169
+ ): Promise<void> {
170
+ const toSend: SocketMessage = {
171
+ response: {
172
+ payload: result,
173
+ id: requestId,
174
+ error: null,
175
+ },
176
+ };
177
+ return this.ws.send(JSON.stringify(toSend));
178
+ }
179
+
180
+ private async respondError(
181
+ requestId: number,
182
+ error: SocketError
183
+ ): Promise<void> {
184
+ const toSend: SocketMessage = {
185
+ response: {
186
+ payload: null,
187
+ id: requestId,
188
+ error: error,
189
+ },
190
+ };
191
+ return this.ws.send(JSON.stringify(toSend));
192
+ }
193
+
194
+ public close(): void {
195
+ this.ws.close();
196
+ }
197
+ }
198
+
199
+ const proxyTarget = Object.freeze(() => {
200
+ /* return nothing */
201
+ });
202
+
203
+ function createIsocketProxy<
204
+ ImplementedMethods,
205
+ CallableMethods,
206
+ RequestContext
207
+ >(
208
+ socket: ISocket<ImplementedMethods, CallableMethods, RequestContext>,
209
+ // if path is undefined, it means the current object is the root object
210
+ path: string | undefined
211
+ ): unknown {
212
+ return new Proxy(proxyTarget, {
213
+ get(_target, prop: string) {
214
+ const childPath = path ? `${path}.${prop}` : prop;
215
+ // sometimes, when `createISocketClient` is called from an async function, JS will implicitly call the `then` method on
216
+ // its return value, because promises can be arbitrarily nested
217
+ // so return undefined for the `then` method to avoid this
218
+ if (childPath === "then") {
219
+ return undefined;
220
+ }
221
+ return createIsocketProxy(socket, childPath);
222
+ },
223
+
224
+ apply(_target, _thisArg, args: unknown[]) {
225
+ if (path === undefined) {
226
+ throw new Error("The root object is not callable");
227
+ }
228
+ if (
229
+ path.endsWith(".apply") &&
230
+ args.length === 2 &&
231
+ Array.isArray(args[1])
232
+ ) {
233
+ path = path.slice(0, -".apply".length);
234
+ args = args[1];
235
+ }
236
+ return socket.request(path, args[0]);
237
+ },
238
+ });
239
+ }
240
+
241
+ export function createISocketClient<
242
+ CallableMethods,
243
+ ImplementedMethods,
244
+ RequestContext
245
+ >(
246
+ socket: ISocket<ImplementedMethods, CallableMethods, RequestContext>
247
+ ): ISocketClient<CallableMethods> {
248
+ return {
249
+ close: () => socket.close(),
250
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment
251
+ call: createIsocketProxy(socket, undefined) as any,
252
+ };
253
+ }
@@ -0,0 +1,138 @@
1
+ export interface UserMeDto {
2
+ user: User;
3
+ organizations: Organization[];
4
+ agents: Agent[];
5
+ flagBootstrap: FlagBootstrap;
6
+ }
7
+
8
+ export interface FlagBootstrap {
9
+ "ui.enable-resource-signing"?: boolean;
10
+ }
11
+
12
+ export type User = {
13
+ id: string;
14
+ email: string;
15
+ currentOrganizationId: string;
16
+ organizationIds: string[];
17
+ username: string;
18
+ name: string;
19
+ anonymousId: string;
20
+ isAnonymous: boolean;
21
+ isAdmin: boolean;
22
+ metadata: Record<string, unknown>;
23
+ };
24
+
25
+ export interface Organization {
26
+ id: string;
27
+ name: string;
28
+ displayName: string;
29
+ agents?: Agent[];
30
+ apiKey: string;
31
+ agentType: AgentType;
32
+ minExternalAgentVersion: string;
33
+ profiles?: Profile[];
34
+ }
35
+
36
+ export type Agent = {
37
+ id: string;
38
+ key: string;
39
+ environment: string;
40
+ status: AgentStatus;
41
+ version: string;
42
+ versionExternal: string;
43
+ url: string;
44
+ type: AgentType;
45
+ updated: Date;
46
+ created: Date;
47
+ tags: AgentTags;
48
+ verificationKeyIds?: null | string[];
49
+ signingKeyId?: null | string;
50
+ };
51
+
52
+ export enum AgentStatus {
53
+ ACTIVE = "Active",
54
+ DISCONNECTED = "Disconnected",
55
+ BROWSER_UNREACHABLE = "Browser Unreachable",
56
+ // TODO: remove PENDING_REGISTRATION after the DB migration
57
+ PENDING_REGISTRATION = "Pending Registration",
58
+ STALE = "Stale",
59
+ }
60
+
61
+ export enum AgentType {
62
+ MULTITENANT = 0,
63
+ DEDICATED = 1,
64
+ ONPREMISE = 2,
65
+ }
66
+
67
+ export type AgentTags = Record<string, string[]>;
68
+
69
+ export class Profile {
70
+ id: string;
71
+ key: string;
72
+ displayName: string;
73
+ description: string;
74
+ type: ProfileType;
75
+
76
+ constructor({
77
+ id,
78
+ key,
79
+ displayName,
80
+ description,
81
+ type,
82
+ }: {
83
+ id: string;
84
+ key: string;
85
+ displayName: string;
86
+ description: string;
87
+ type: ProfileType;
88
+ }) {
89
+ this.id = id;
90
+ this.key = key;
91
+ this.displayName = displayName;
92
+ this.description = description;
93
+ this.type = type;
94
+ }
95
+ }
96
+
97
+ export enum ProfileType {
98
+ RESERVED = "RESERVED",
99
+ CUSTOM = "CUSTOM",
100
+ }
101
+
102
+ export type Api = {
103
+ metadata: {
104
+ name: string;
105
+ id: string;
106
+ organization: string;
107
+ timestamps?: {
108
+ created: string;
109
+ updated: string;
110
+ deactivated: boolean;
111
+ };
112
+ // These properties are merged in from the v3 api entity
113
+ creator?: {
114
+ id: string;
115
+ name: string;
116
+ };
117
+ folder?: string;
118
+ };
119
+ blocks?: any[];
120
+ trigger: any;
121
+ signature?: Signature;
122
+ };
123
+
124
+ /** A signature, as produced by the agent. */
125
+ export interface Signature {
126
+ /** The id of the key used to sign the data. */
127
+ keyId: string;
128
+ /** The actual signature, in base64. */
129
+ data: string;
130
+ }
131
+
132
+ export interface RemoteCommitDto {
133
+ commitId: string;
134
+ remoteCommitId: string;
135
+ remoteCommitDate: Date;
136
+ branchName: string;
137
+ repositoryId: string;
138
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./common";
2
+ export * from "./socket";
3
+ export * from "./plugin";
4
+ export * from "./signing";
@@ -0,0 +1,7 @@
1
+ export const transcribeAudioToTextTranslateToEnglishTruthyValues = [
2
+ "checked",
3
+ "true",
4
+ true,
5
+ ];
6
+
7
+ export const OpenAiPluginId = "openai";
@@ -0,0 +1,61 @@
1
+ import { Api as ApiPb, Signature } from "./common";
2
+
3
+ export type SignatureResponse = {
4
+ signature: Signature;
5
+ };
6
+
7
+ export type ApiResource = {
8
+ api: ApiPb;
9
+ };
10
+
11
+ export type GenericResource = {
12
+ literal: {
13
+ data: any;
14
+ signature?: Signature;
15
+ };
16
+ };
17
+
18
+ interface PageHash {
19
+ /** The version of the page DSL. */
20
+ version: number;
21
+ /** The SHA-256 hash of the page DSL (i.e. `page.layouts[0].dsl`), in base64. */
22
+ hash: string;
23
+ }
24
+
25
+ export interface ApplicationSettingsHashes {
26
+ /** The SHA-256 hash of all custom components related properties from settings, in base64. */
27
+ components: string;
28
+ /** The SHA-256 hash of settings without the custom components related properties, in base64. */
29
+ rest: string;
30
+ }
31
+
32
+ export interface ApplicationSignatureTree {
33
+ /** The version of the signature tree. */
34
+ v: 1;
35
+ settings: ApplicationSettingsHashes;
36
+ page: PageHash;
37
+ }
38
+
39
+ export interface ApplicationSignatureTreeSigned {
40
+ /** The SHA-256 hash of `root`, in base64. */
41
+ signature: Signature;
42
+ root: ApplicationSignatureTree;
43
+ }
44
+
45
+ export type AppToSign = {
46
+ rootHash: string;
47
+ };
48
+
49
+ export type AppToVerify = {
50
+ rootHash: string;
51
+ signature?: Signature;
52
+ };
53
+
54
+ export type ApiToSign = {
55
+ apiPb: ApiPb;
56
+ };
57
+
58
+ export type ApiToVerify = {
59
+ apiPb: ApiPb;
60
+ signature?: Signature;
61
+ };
@@ -0,0 +1,48 @@
1
+ export type MethodHandler<Params, Result, PeerMethods, RequestContext> = (
2
+ params: Params,
3
+ peer: ISocketClient<PeerMethods>,
4
+ ctx: RequestContext
5
+ ) => Promise<Result>;
6
+
7
+ export type MiddlewareHandler<Params, PeerMethods, RequestContext> = (
8
+ params: Params,
9
+ peerAuthorization: string | undefined,
10
+ peer: ISocketClient<PeerMethods>,
11
+ ctx: RequestContext
12
+ ) => Promise<void>;
13
+
14
+ export type MethodHandlers<Methods, PeerMethods, RequestContext> = {
15
+ [Key in keyof Methods]: Methods[Key] extends (
16
+ params: infer Params
17
+ ) => Promise<infer Result>
18
+ ? [
19
+ ...middlewareHandlers: MiddlewareHandler<
20
+ Params,
21
+ PeerMethods,
22
+ RequestContext
23
+ >[],
24
+ handler: MethodHandler<Params, Result, PeerMethods, RequestContext>
25
+ ]
26
+ : Methods[Key] extends Record<string, unknown>
27
+ ? MethodHandlers<Methods[Key], PeerMethods, RequestContext>
28
+ : never;
29
+ };
30
+
31
+ type ISocketClientMethodCall<Methods> = {
32
+ [Key in keyof Methods]: Methods[Key] extends (
33
+ params: infer P
34
+ ) => Promise<infer R>
35
+ ? (params: P) => Promise<R>
36
+ : Methods[Key] extends Record<string, unknown>
37
+ ? ISocketClientMethodCall<Methods[Key]>
38
+ : never;
39
+ };
40
+
41
+ export type ISocketClient<Methods> = {
42
+ close: () => void;
43
+ call: ISocketClientMethodCall<Methods>;
44
+ };
45
+
46
+ export type MethodSchema<Params, Response> = (
47
+ params: Params
48
+ ) => Promise<Response>;
package/src/utils.ts ADDED
@@ -0,0 +1,81 @@
1
+ import axios, { AxiosRequestConfig } from "axios";
2
+ import {
3
+ Agent,
4
+ AgentStatus,
5
+ AgentType,
6
+ Api,
7
+ OpenAiPluginId,
8
+ transcribeAudioToTextTranslateToEnglishTruthyValues,
9
+ } from "./types";
10
+
11
+ export async function getAgentUrl(
12
+ agents: Agent[],
13
+ agentType: AgentType,
14
+ profile?: string,
15
+ healthCheck = true
16
+ ): Promise<string> {
17
+ let filtered = agents.filter(
18
+ (a) => a.type === agentType && a.status === AgentStatus.ACTIVE
19
+ );
20
+ if (profile) {
21
+ filtered = agents.filter(
22
+ (a) =>
23
+ a.tags.profile &&
24
+ (a.tags.profile.includes("*") || a.tags.profile.includes(profile))
25
+ );
26
+ }
27
+ if (healthCheck) {
28
+ const coroutines: Promise<string>[] = [];
29
+ for (const agent of filtered) {
30
+ const p: Promise<string> = (async () => {
31
+ const url = new URL("health", agent.url);
32
+ const config: AxiosRequestConfig = {
33
+ url: url.toString(),
34
+ method: "get",
35
+ headers: {},
36
+ };
37
+ await axios(config);
38
+ return agent.url;
39
+ })();
40
+ coroutines.push(p);
41
+ }
42
+ try {
43
+ return await Promise.any(coroutines);
44
+ } catch (e) {
45
+ throw new Error("No available agents");
46
+ }
47
+ } else {
48
+ const randomIndex = Math.floor(Math.random() * filtered.length);
49
+ return filtered[randomIndex].url;
50
+ }
51
+ }
52
+
53
+ export const sanitizeV2RequestBody = (
54
+ apiBody: Record<string, unknown>
55
+ ): Record<string, unknown> => {
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ const traverseJSON = (obj: any) => {
58
+ for (const key in obj) {
59
+ if (key === "step" && obj.step[OpenAiPluginId]) {
60
+ const value =
61
+ obj.step[OpenAiPluginId].transcribeAudioToTextTranslateToEnglish;
62
+ obj.step[OpenAiPluginId].transcribeAudioToTextTranslateToEnglish =
63
+ transcribeAudioToTextTranslateToEnglishTruthyValues.includes(value);
64
+ } else if (typeof obj[key] === "object") {
65
+ sanitizeV2RequestBody(obj[key]);
66
+ }
67
+ }
68
+ };
69
+ traverseJSON(apiBody);
70
+ return apiBody;
71
+ };
72
+
73
+ export const getSanitizedApi = (api: Api): any => {
74
+ const sanitizedBlocks = (api.blocks ?? [])?.map(
75
+ (block: Record<string, unknown>) => sanitizeV2RequestBody(block)
76
+ );
77
+ return {
78
+ ...api,
79
+ blocks: sanitizedBlocks,
80
+ };
81
+ };
package/tsconfig.json CHANGED
@@ -6,7 +6,9 @@
6
6
  "outDir": "dist",
7
7
  "rootDir": "src",
8
8
  "strict": true,
9
- "target": "es2019"
9
+ "target": "es2021",
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true
10
12
  },
11
13
  "include": ["src/**/*"]
12
14
  }