@superblocksteam/sdk 1.4.2 → 1.5.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.
@@ -0,0 +1,164 @@
1
+ import WebSocket from "isomorphic-ws";
2
+ import {
3
+ ClientMethods,
4
+ createRequestHandlers,
5
+ ServerMethods,
6
+ } from "./handlers";
7
+ import { createISocketClient, ISocket } from "./socket";
8
+
9
+ export type StdISocketRPCClient = ISocketClient<ServerMethods>;
10
+
11
+ export async function connectToISocketRPCServer({
12
+ superblocksBaseUrl,
13
+ agentUrls,
14
+ token,
15
+ }: {
16
+ superblocksBaseUrl: string;
17
+ agentUrls: string[];
18
+ token: string;
19
+ }): Promise<StdISocketRPCClient> {
20
+ const requestHandlers = createRequestHandlers({
21
+ agentUrls,
22
+ token,
23
+ });
24
+ const authorization = `Bearer ${token}`;
25
+ const wsUrl = new URL("api/v1/rpc-ws", superblocksBaseUrl);
26
+ if (wsUrl.protocol === "http:") {
27
+ wsUrl.protocol = "ws:";
28
+ } else if (wsUrl.protocol === "https:") {
29
+ wsUrl.protocol = "wss:";
30
+ }
31
+
32
+ if (wsUrl.host === "localhost:3000") {
33
+ wsUrl.host = "127.0.0.1:8080";
34
+ } else if (wsUrl.hostname === "localhost") {
35
+ wsUrl.hostname = "127.0.0.1";
36
+ }
37
+ return await connectISocket<ServerMethods, ClientMethods, unknown>(
38
+ wsUrl.href,
39
+ authorization,
40
+ requestHandlers
41
+ );
42
+ }
43
+
44
+ // a subclass of ISocket that sends an auth token on the first request
45
+ // this is useful for client-side sockets that need to authenticate
46
+ // TODO(george): if we start using this for long-lived connections, we should add a way to refresh the token
47
+ class ISocketWithClientAuth<
48
+ ImplementedMethods,
49
+ CallableMethods,
50
+ RequestContext = void
51
+ > extends ISocket<ImplementedMethods, CallableMethods, RequestContext> {
52
+ private readonly authorization?: string;
53
+ private hasSentAuth = false;
54
+
55
+ constructor(
56
+ ws: WebSocket,
57
+ authorization: string | undefined,
58
+ requestHandlers: MethodHandlers<
59
+ ImplementedMethods,
60
+ CallableMethods,
61
+ RequestContext
62
+ >
63
+ ) {
64
+ super(ws, requestHandlers);
65
+ this.authorization = authorization;
66
+ }
67
+
68
+ // override `request` from the base class to send `authorization` when appropriate
69
+ async request<Params, Result>(
70
+ method: string,
71
+ params: Params
72
+ ): Promise<Result> {
73
+ // only send `authorization` on the first request
74
+ const authorization = this.hasSentAuth ? undefined : this.authorization;
75
+ const result = await super.request<Params, Result>(
76
+ method,
77
+ params,
78
+ authorization
79
+ );
80
+ this.hasSentAuth = true;
81
+ return result;
82
+ }
83
+ }
84
+
85
+ export async function connectISocket<
86
+ CallableMethods,
87
+ ImplementedMethods,
88
+ RequestContext = never
89
+ >(
90
+ wsUrl: string,
91
+ authorization: string | undefined,
92
+ requestHandlers: MethodHandlers<
93
+ ImplementedMethods,
94
+ CallableMethods,
95
+ RequestContext
96
+ >
97
+ ): Promise<ISocketClient<CallableMethods>> {
98
+ const ws = await connectWebSocket(wsUrl);
99
+ const isocket = new ISocketWithClientAuth(ws, authorization, requestHandlers);
100
+ return createISocketClient(isocket);
101
+ }
102
+
103
+ export function connectWebSocket(wsUrl: string): Promise<WebSocket> {
104
+ return new Promise((resolve, reject) => {
105
+ const ws = new WebSocket(wsUrl);
106
+
107
+ ws.addEventListener("open", () => {
108
+ // Resolve the promise with the WebSocket instance when the connection is open
109
+ resolve(ws);
110
+ });
111
+
112
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
113
+ // @ts-ignore
114
+ ws.addEventListener("error", (error: Error) => {
115
+ // Reject the promise if there's an error
116
+ reject(error);
117
+ });
118
+ });
119
+ }
120
+
121
+ type MethodHandler<Params, Result, PeerMethods, RequestContext> = (
122
+ params: Params,
123
+ peer: ISocketClient<PeerMethods>,
124
+ ctx: RequestContext
125
+ ) => Promise<Result>;
126
+
127
+ type MiddlewareHandler<Params, PeerMethods, RequestContext> = (
128
+ params: Params,
129
+ peerAuthorization: string | undefined,
130
+ peer: ISocketClient<PeerMethods>,
131
+ ctx: RequestContext
132
+ ) => Promise<void>;
133
+
134
+ type MethodHandlers<Methods, PeerMethods, RequestContext> = {
135
+ [Key in keyof Methods]: Methods[Key] extends (
136
+ params: infer Params
137
+ ) => Promise<infer Result>
138
+ ? [
139
+ ...middlewareHandlers: MiddlewareHandler<
140
+ Params,
141
+ PeerMethods,
142
+ RequestContext
143
+ >[],
144
+ handler: MethodHandler<Params, Result, PeerMethods, RequestContext>
145
+ ]
146
+ : Methods[Key] extends Record<string, unknown>
147
+ ? MethodHandlers<Methods[Key], PeerMethods, RequestContext>
148
+ : never;
149
+ };
150
+
151
+ type ISocketClientMethodCall<Methods> = {
152
+ [Key in keyof Methods]: Methods[Key] extends (
153
+ params: infer P
154
+ ) => Promise<infer R>
155
+ ? (params: P) => Promise<R>
156
+ : Methods[Key] extends Record<string, unknown>
157
+ ? ISocketClientMethodCall<Methods[Key]>
158
+ : never;
159
+ };
160
+
161
+ type ISocketClient<Methods> = {
162
+ close: () => void;
163
+ call: ISocketClientMethodCall<Methods>;
164
+ };
@@ -0,0 +1,113 @@
1
+ import axios, { AxiosRequestConfig, Method } from "axios";
2
+ import {
3
+ ApiResource,
4
+ GenericResource,
5
+ Signature,
6
+ SignatureResponse,
7
+ } from "../types";
8
+ import { getSanitizedApi } from "../utils";
9
+
10
+ export async function signResource({
11
+ token,
12
+ branchName,
13
+ resource,
14
+ agentUrls,
15
+ }: {
16
+ token: string;
17
+ branchName: string;
18
+ resource: ApiResource | GenericResource;
19
+ agentUrls: string[];
20
+ }): Promise<Signature> {
21
+ const requestResource: Record<string, any> = {
22
+ branchName: branchName ?? "main",
23
+ };
24
+ if ((resource as ApiResource).api) {
25
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
26
+ requestResource.api = getSanitizedApi((resource as ApiResource).api);
27
+ } else {
28
+ requestResource.literal = (resource as GenericResource).literal;
29
+ }
30
+
31
+ try {
32
+ const resp = await callAgentFallBack<SignatureResponse>({
33
+ baseUrls: agentUrls,
34
+ path: "v1/signature/sign",
35
+ method: "post",
36
+ token: token,
37
+ data: { resource: requestResource },
38
+ });
39
+ return resp.signature;
40
+ } catch (e) {
41
+ throw new Error("No agents available to sign the resource");
42
+ }
43
+ }
44
+
45
+ export async function verifyResources({
46
+ agentUrls,
47
+ token,
48
+ branchName,
49
+ resources,
50
+ }: {
51
+ resources: Array<GenericResource | ApiResource>;
52
+ token: string;
53
+ branchName: string;
54
+ agentUrls: string[];
55
+ }): Promise<void> {
56
+ try {
57
+ await callAgentFallBack<{ keyId: string }>({
58
+ baseUrls: agentUrls,
59
+ path: "v1/signature/verify",
60
+ method: "post",
61
+ token: token,
62
+ data: {
63
+ resources: resources.map((res) => {
64
+ if ((res as ApiResource).api) {
65
+ return {
66
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
67
+ api: getSanitizedApi((res as ApiResource).api),
68
+ branchName: branchName ?? "main",
69
+ };
70
+ }
71
+ return {
72
+ ...res,
73
+ branchName: branchName ?? "main",
74
+ };
75
+ }),
76
+ },
77
+ });
78
+ } catch (e) {
79
+ throw new Error("No agents available to verify the resource");
80
+ }
81
+ }
82
+
83
+ async function callAgentFallBack<T>({
84
+ baseUrls,
85
+ path,
86
+ method,
87
+ token,
88
+ data,
89
+ }: {
90
+ baseUrls: string[];
91
+ path: string;
92
+ method: Method;
93
+ token: string;
94
+ data: any;
95
+ }): Promise<T> {
96
+ for (const baseUrl of baseUrls) {
97
+ try {
98
+ const url = new URL(path, baseUrl);
99
+ const config: AxiosRequestConfig = {
100
+ url: url.toString(),
101
+ method: method,
102
+ headers: {
103
+ Authorization: "Bearer " + token,
104
+ },
105
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
106
+ data: data,
107
+ };
108
+ const resp = await axios<T>(config);
109
+ return resp.data;
110
+ } catch (e) {}
111
+ }
112
+ throw new Error("Failed to request ");
113
+ }
@@ -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";