@dxos/edge-client 0.8.4-main.fd6878d → 0.8.4-staging.60fe92afc8
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/LICENSE +102 -5
- package/dist/lib/{node-esm/chunk-R6K4IIBW.mjs → neutral/chunk-L5ZHLJ4B.mjs} +57 -53
- package/dist/lib/neutral/chunk-L5ZHLJ4B.mjs.map +7 -0
- package/dist/lib/neutral/chunk-WQKMEZJR.mjs +30 -0
- package/dist/lib/neutral/chunk-WQKMEZJR.mjs.map +7 -0
- package/dist/lib/neutral/cors-proxy.mjs +7 -0
- package/dist/lib/{browser → neutral}/edge-ws-muxer.mjs +1 -1
- package/dist/lib/neutral/index.mjs +1448 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/{browser → neutral}/testing/index.mjs +6 -31
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/auth.d.ts.map +1 -1
- package/dist/types/src/base-http-client.d.ts +48 -0
- package/dist/types/src/base-http-client.d.ts.map +1 -0
- package/dist/types/src/cors-proxy.d.ts +6 -0
- package/dist/types/src/cors-proxy.d.ts.map +1 -0
- package/dist/types/src/edge-ai-http-client.d.ts +65 -0
- package/dist/types/src/edge-ai-http-client.d.ts.map +1 -0
- package/dist/types/src/edge-client.d.ts +17 -14
- package/dist/types/src/edge-client.d.ts.map +1 -1
- package/dist/types/src/edge-http-client.d.ts +81 -54
- package/dist/types/src/edge-http-client.d.ts.map +1 -1
- package/dist/types/src/edge-identity.d.ts.map +1 -1
- package/dist/types/src/edge-ws-connection.d.ts +20 -0
- package/dist/types/src/edge-ws-connection.d.ts.map +1 -1
- package/dist/types/src/edge-ws-muxer.d.ts.map +1 -1
- package/dist/types/src/errors.d.ts.map +1 -1
- package/dist/types/src/http-client.d.ts +10 -7
- package/dist/types/src/http-client.d.ts.map +1 -1
- package/dist/types/src/hub-http-client.d.ts +39 -0
- package/dist/types/src/hub-http-client.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +8 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/protocol.d.ts +1 -1
- package/dist/types/src/protocol.d.ts.map +1 -1
- package/dist/types/src/testing/test-server.d.ts.map +1 -1
- package/dist/types/src/testing/test-utils.d.ts +3 -3
- package/dist/types/src/testing/test-utils.d.ts.map +1 -1
- package/dist/types/src/utils.d.ts +1 -1
- package/dist/types/src/utils.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +33 -32
- package/src/base-http-client.ts +243 -0
- package/src/cors-proxy.ts +38 -0
- package/src/edge-ai-http-client.ts +129 -0
- package/src/edge-client.test.ts +20 -15
- package/src/edge-client.ts +105 -44
- package/src/edge-http-client.test.ts +37 -3
- package/src/edge-http-client.ts +312 -190
- package/src/edge-ws-connection.ts +120 -6
- package/src/edge-ws-muxer.ts +49 -5
- package/src/http-client.test.ts +11 -7
- package/src/http-client.ts +18 -8
- package/src/hub-http-client.ts +118 -0
- package/src/index.ts +8 -3
- package/src/testing/test-utils.ts +7 -7
- package/dist/lib/browser/chunk-SUXH7FH6.mjs +0 -304
- package/dist/lib/browser/chunk-SUXH7FH6.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -1124
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-R6K4IIBW.mjs.map +0 -7
- package/dist/lib/node-esm/edge-ws-muxer.mjs +0 -12
- package/dist/lib/node-esm/index.mjs +0 -1125
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -186
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- /package/dist/lib/{browser/edge-ws-muxer.mjs.map → neutral/cors-proxy.mjs.map} +0 -0
- /package/dist/lib/{node-esm → neutral}/edge-ws-muxer.mjs.map +0 -0
package/src/edge-http-client.ts
CHANGED
|
@@ -2,120 +2,120 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
import
|
|
5
|
+
import * as FetchHttpClient from '@effect/platform/FetchHttpClient';
|
|
6
|
+
import * as HttpClient from '@effect/platform/HttpClient';
|
|
7
|
+
import * as HttpClientRequest from '@effect/platform/HttpClientRequest';
|
|
8
|
+
import * as Effect from 'effect/Effect';
|
|
9
|
+
import * as Function from 'effect/Function';
|
|
10
|
+
|
|
11
|
+
import { type Context } from '@dxos/context';
|
|
12
|
+
import { EffectEx } from '@dxos/effect';
|
|
13
|
+
import { invariant } from '@dxos/invariant';
|
|
10
14
|
import { type PublicKey, type SpaceId } from '@dxos/keys';
|
|
11
15
|
import { log } from '@dxos/log';
|
|
12
16
|
import {
|
|
17
|
+
type CompleteOAuthRegistrationRequest,
|
|
18
|
+
type CompleteOAuthRegistrationResponse,
|
|
13
19
|
type CreateAgentRequestBody,
|
|
14
20
|
type CreateAgentResponseBody,
|
|
15
21
|
type CreateSpaceRequest,
|
|
16
22
|
type CreateSpaceResponseBody,
|
|
17
|
-
|
|
18
|
-
EdgeCallFailedError,
|
|
19
|
-
type EdgeHttpResponse,
|
|
23
|
+
EDGE_CLIENT_TAG_HEADER,
|
|
20
24
|
type EdgeStatus,
|
|
21
25
|
type ExecuteWorkflowResponseBody,
|
|
26
|
+
type ExportBundleRequest,
|
|
27
|
+
type ExportBundleResponse,
|
|
28
|
+
type FeedProtocol,
|
|
22
29
|
type GetAgentStatusResponseBody,
|
|
23
30
|
type GetNotarizationResponseBody,
|
|
31
|
+
type GetPluginVersionsResponseBody,
|
|
32
|
+
type GetPluginsResponseBody,
|
|
33
|
+
type ImportBundleRequest,
|
|
24
34
|
type InitiateOAuthFlowRequest,
|
|
25
35
|
type InitiateOAuthFlowResponse,
|
|
26
36
|
type JoinSpaceRequest,
|
|
27
37
|
type JoinSpaceResponseBody,
|
|
28
38
|
type ObjectId,
|
|
29
39
|
type PostNotarizationRequestBody,
|
|
30
|
-
type QueryResult,
|
|
31
|
-
type QueueQuery,
|
|
32
40
|
type RecoverIdentityRequest,
|
|
33
41
|
type RecoverIdentityResponseBody,
|
|
34
42
|
type UploadFunctionRequest,
|
|
35
43
|
type UploadFunctionResponseBody,
|
|
36
44
|
} from '@dxos/protocols';
|
|
45
|
+
import {
|
|
46
|
+
type QueryRequest as QueryRequestProto,
|
|
47
|
+
type QueryResponse as QueryResponseProto,
|
|
48
|
+
} from '@dxos/protocols/proto/dxos/echo/query';
|
|
37
49
|
import { createUrl } from '@dxos/util';
|
|
38
50
|
|
|
39
|
-
import { type
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
51
|
+
import { BaseHttpClient, type BaseHttpClientOptions, type EdgeHttpCallArgs } from './base-http-client';
|
|
52
|
+
import { proxyFetchLegacy } from './cors-proxy';
|
|
53
|
+
import { HttpConfig, withLogging, withRetryConfig } from './http-client';
|
|
42
54
|
|
|
43
|
-
|
|
44
|
-
const DEFAULT_RETRY_JITTER = 500;
|
|
45
|
-
const DEFAULT_MAX_RETRIES_COUNT = 3;
|
|
55
|
+
export type { EdgeHttpCallArgs, RetryConfig } from './base-http-client';
|
|
46
56
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
*/
|
|
55
|
-
timeout?: number;
|
|
56
|
-
/**
|
|
57
|
-
* A random amount of time before retrying to help prevent large bursts of requests.
|
|
58
|
-
*/
|
|
59
|
-
jitter?: number;
|
|
57
|
+
/**
|
|
58
|
+
* HTTP wire shape returned by `/queue/.../query`.
|
|
59
|
+
*/
|
|
60
|
+
export type EdgeQueryQueueResponse = {
|
|
61
|
+
objects?: unknown[];
|
|
62
|
+
nextCursor?: string;
|
|
63
|
+
prevCursor?: string;
|
|
60
64
|
};
|
|
61
65
|
|
|
62
|
-
type
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
export type TriggersDispatcherStatus = {
|
|
67
|
+
isActive: boolean;
|
|
68
|
+
nextCronTaskRunTimestamp?: number;
|
|
69
|
+
registeredTriggers: string[];
|
|
70
|
+
stopAfterTimestamp?: number;
|
|
71
|
+
remainingMs?: number;
|
|
72
|
+
nextAlarmTimestamp?: number;
|
|
67
73
|
};
|
|
68
74
|
|
|
69
|
-
export type
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
export class EdgeHttpClient {
|
|
73
|
-
private readonly _baseUrl: string;
|
|
74
|
-
|
|
75
|
-
private _edgeIdentity: EdgeIdentity | undefined;
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Auth header is cached until receiving the next 401 from EDGE, at which point it gets refreshed.
|
|
79
|
-
*/
|
|
80
|
-
private _authHeader: string | undefined;
|
|
81
|
-
|
|
82
|
-
constructor(baseUrl: string) {
|
|
83
|
-
this._baseUrl = getEdgeUrlWithProtocol(baseUrl, 'http');
|
|
84
|
-
log('created', { url: this._baseUrl });
|
|
85
|
-
}
|
|
75
|
+
export type GetCronTriggersResponse = {
|
|
76
|
+
cronIds: string[];
|
|
77
|
+
};
|
|
86
78
|
|
|
87
|
-
|
|
88
|
-
return this._baseUrl;
|
|
89
|
-
}
|
|
79
|
+
export type EdgeHttpClientOptions = BaseHttpClientOptions;
|
|
90
80
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
81
|
+
/**
|
|
82
|
+
* HTTP client for the edge worker API (spaces, queues, functions, agents, etc.).
|
|
83
|
+
*
|
|
84
|
+
* Hub-service API (accounts, invitations) lives in {@link HubHttpClient} — the two
|
|
85
|
+
* services run at different URLs and are never both available from the same base URL.
|
|
86
|
+
*/
|
|
87
|
+
export class EdgeHttpClient extends BaseHttpClient {
|
|
88
|
+
constructor(baseUrl: string, options?: EdgeHttpClientOptions) {
|
|
89
|
+
super(baseUrl, options);
|
|
90
|
+
log('created', { url: this.baseUrl });
|
|
96
91
|
}
|
|
97
92
|
|
|
98
93
|
//
|
|
99
94
|
// Status
|
|
100
95
|
//
|
|
101
96
|
|
|
102
|
-
public async getStatus(args?:
|
|
103
|
-
return this._call(new URL('/status', this.baseUrl), { ...args, method: 'GET' });
|
|
97
|
+
public async getStatus(ctx: Context, args?: EdgeHttpCallArgs): Promise<EdgeStatus> {
|
|
98
|
+
return this._call(ctx, new URL('/status', this.baseUrl), { ...args, method: 'GET', auth: true });
|
|
104
99
|
}
|
|
105
100
|
|
|
106
101
|
//
|
|
107
102
|
// Agents
|
|
108
103
|
//
|
|
109
104
|
|
|
110
|
-
public createAgent(
|
|
111
|
-
|
|
105
|
+
public createAgent(
|
|
106
|
+
ctx: Context,
|
|
107
|
+
body: CreateAgentRequestBody,
|
|
108
|
+
args?: EdgeHttpCallArgs,
|
|
109
|
+
): Promise<CreateAgentResponseBody> {
|
|
110
|
+
return this._call(ctx, new URL('/agents/create', this.baseUrl), { ...args, method: 'POST', body });
|
|
112
111
|
}
|
|
113
112
|
|
|
114
113
|
public getAgentStatus(
|
|
114
|
+
ctx: Context,
|
|
115
115
|
request: { ownerIdentityKey: PublicKey },
|
|
116
|
-
args?:
|
|
116
|
+
args?: EdgeHttpCallArgs,
|
|
117
117
|
): Promise<GetAgentStatusResponseBody> {
|
|
118
|
-
return this._call(new URL(`/users/${request.ownerIdentityKey.toHex()}/agent/status`, this.baseUrl), {
|
|
118
|
+
return this._call(ctx, new URL(`/users/${request.ownerIdentityKey.toHex()}/agent/status`, this.baseUrl), {
|
|
119
119
|
...args,
|
|
120
120
|
method: 'GET',
|
|
121
121
|
});
|
|
@@ -125,16 +125,21 @@ export class EdgeHttpClient {
|
|
|
125
125
|
// Credentials
|
|
126
126
|
//
|
|
127
127
|
|
|
128
|
-
public getCredentialsForNotarization(
|
|
129
|
-
|
|
128
|
+
public getCredentialsForNotarization(
|
|
129
|
+
ctx: Context,
|
|
130
|
+
spaceId: SpaceId,
|
|
131
|
+
args?: EdgeHttpCallArgs,
|
|
132
|
+
): Promise<GetNotarizationResponseBody> {
|
|
133
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), { ...args, method: 'GET' });
|
|
130
134
|
}
|
|
131
135
|
|
|
132
136
|
public async notarizeCredentials(
|
|
137
|
+
ctx: Context,
|
|
133
138
|
spaceId: SpaceId,
|
|
134
139
|
body: PostNotarizationRequestBody,
|
|
135
|
-
args?:
|
|
140
|
+
args?: EdgeHttpCallArgs,
|
|
136
141
|
): Promise<void> {
|
|
137
|
-
await this._call(new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), { ...args, body, method: 'POST' });
|
|
142
|
+
await this._call(ctx, new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), { ...args, body, method: 'POST' });
|
|
138
143
|
}
|
|
139
144
|
|
|
140
145
|
//
|
|
@@ -142,45 +147,52 @@ export class EdgeHttpClient {
|
|
|
142
147
|
//
|
|
143
148
|
|
|
144
149
|
public async recoverIdentity(
|
|
150
|
+
ctx: Context,
|
|
145
151
|
body: RecoverIdentityRequest,
|
|
146
|
-
args?:
|
|
152
|
+
args?: EdgeHttpCallArgs,
|
|
147
153
|
): Promise<RecoverIdentityResponseBody> {
|
|
148
|
-
return this._call(new URL('/identity/recover', this.baseUrl), { ...args, body, method: 'POST' });
|
|
154
|
+
return this._call(ctx, new URL('/identity/recover', this.baseUrl), { ...args, body, method: 'POST' });
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
//
|
|
152
|
-
// Invitations
|
|
158
|
+
// Invitations (space join)
|
|
153
159
|
//
|
|
154
160
|
|
|
155
161
|
public async joinSpaceByInvitation(
|
|
162
|
+
ctx: Context,
|
|
156
163
|
spaceId: SpaceId,
|
|
157
164
|
body: JoinSpaceRequest,
|
|
158
|
-
args?:
|
|
165
|
+
args?: EdgeHttpCallArgs,
|
|
159
166
|
): Promise<JoinSpaceResponseBody> {
|
|
160
|
-
return this._call(new URL(`/spaces/${spaceId}/join`, this.baseUrl), { ...args, body, method: 'POST' });
|
|
167
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/join`, this.baseUrl), { ...args, body, method: 'POST' });
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
//
|
|
164
|
-
// OAuth
|
|
171
|
+
// OAuth
|
|
165
172
|
//
|
|
166
173
|
|
|
167
|
-
public async listFunctions(args?: EdgeHttpGetArgs): Promise<any> {
|
|
168
|
-
return this._call(new URL('/functions', this.baseUrl), { ...args, method: 'GET' });
|
|
169
|
-
}
|
|
170
|
-
|
|
171
174
|
public async initiateOAuthFlow(
|
|
175
|
+
ctx: Context,
|
|
172
176
|
body: InitiateOAuthFlowRequest,
|
|
173
|
-
args?:
|
|
177
|
+
args?: EdgeHttpCallArgs,
|
|
174
178
|
): Promise<InitiateOAuthFlowResponse> {
|
|
175
|
-
return this._call(new URL('/oauth/initiate', this.baseUrl), { ...args, body, method: 'POST' });
|
|
179
|
+
return this._call(ctx, new URL('/oauth/initiate', this.baseUrl), { ...args, body, method: 'POST' });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public async completeOAuthRegistration(
|
|
183
|
+
ctx: Context,
|
|
184
|
+
body: CompleteOAuthRegistrationRequest,
|
|
185
|
+
args?: EdgeHttpCallArgs,
|
|
186
|
+
): Promise<CompleteOAuthRegistrationResponse> {
|
|
187
|
+
return this._call(ctx, new URL('/oauth/registration/complete', this.baseUrl), { ...args, body, method: 'POST' });
|
|
176
188
|
}
|
|
177
189
|
|
|
178
190
|
//
|
|
179
191
|
// Spaces
|
|
180
192
|
//
|
|
181
193
|
|
|
182
|
-
async createSpace(body: CreateSpaceRequest, args?:
|
|
183
|
-
return this._call(new URL('/spaces/create', this.baseUrl), { ...args, body, method: 'POST' });
|
|
194
|
+
async createSpace(ctx: Context, body: CreateSpaceRequest, args?: EdgeHttpCallArgs): Promise<CreateSpaceResponseBody> {
|
|
195
|
+
return this._call(ctx, new URL('/spaces/create', this.baseUrl), { ...args, body, method: 'POST' });
|
|
184
196
|
}
|
|
185
197
|
|
|
186
198
|
//
|
|
@@ -188,13 +200,16 @@ export class EdgeHttpClient {
|
|
|
188
200
|
//
|
|
189
201
|
|
|
190
202
|
public async queryQueue(
|
|
203
|
+
ctx: Context,
|
|
191
204
|
subspaceTag: string,
|
|
192
205
|
spaceId: SpaceId,
|
|
193
|
-
query: QueueQuery,
|
|
194
|
-
args?:
|
|
195
|
-
): Promise<
|
|
196
|
-
const
|
|
206
|
+
query: FeedProtocol.QueueQuery,
|
|
207
|
+
args?: EdgeHttpCallArgs,
|
|
208
|
+
): Promise<EdgeQueryQueueResponse> {
|
|
209
|
+
const queueId = query.queueIds?.[0];
|
|
210
|
+
invariant(queueId, 'queueId required');
|
|
197
211
|
return this._call(
|
|
212
|
+
ctx,
|
|
198
213
|
createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}/query`, this.baseUrl), {
|
|
199
214
|
after: query.after,
|
|
200
215
|
before: query.before,
|
|
@@ -202,21 +217,19 @@ export class EdgeHttpClient {
|
|
|
202
217
|
reverse: query.reverse,
|
|
203
218
|
objectIds: query.objectIds?.join(','),
|
|
204
219
|
}),
|
|
205
|
-
{
|
|
206
|
-
...args,
|
|
207
|
-
method: 'GET',
|
|
208
|
-
},
|
|
220
|
+
{ ...args, method: 'GET' },
|
|
209
221
|
);
|
|
210
222
|
}
|
|
211
223
|
|
|
212
224
|
public async insertIntoQueue(
|
|
225
|
+
ctx: Context,
|
|
213
226
|
subspaceTag: string,
|
|
214
227
|
spaceId: SpaceId,
|
|
215
228
|
queueId: ObjectId,
|
|
216
229
|
objects: unknown[],
|
|
217
|
-
args?:
|
|
230
|
+
args?: EdgeHttpCallArgs,
|
|
218
231
|
): Promise<void> {
|
|
219
|
-
return this._call(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
|
|
232
|
+
return this._call(ctx, new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
|
|
220
233
|
...args,
|
|
221
234
|
body: { objects },
|
|
222
235
|
method: 'POST',
|
|
@@ -224,20 +237,19 @@ export class EdgeHttpClient {
|
|
|
224
237
|
}
|
|
225
238
|
|
|
226
239
|
public async deleteFromQueue(
|
|
240
|
+
ctx: Context,
|
|
227
241
|
subspaceTag: string,
|
|
228
242
|
spaceId: SpaceId,
|
|
229
243
|
queueId: ObjectId,
|
|
230
244
|
objectIds: ObjectId[],
|
|
231
|
-
args?:
|
|
245
|
+
args?: EdgeHttpCallArgs,
|
|
232
246
|
): Promise<void> {
|
|
233
247
|
return this._call(
|
|
248
|
+
ctx,
|
|
234
249
|
createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
|
|
235
250
|
ids: objectIds.join(','),
|
|
236
251
|
}),
|
|
237
|
-
{
|
|
238
|
-
...args,
|
|
239
|
-
method: 'DELETE',
|
|
240
|
-
},
|
|
252
|
+
{ ...args, method: 'DELETE' },
|
|
241
253
|
);
|
|
242
254
|
}
|
|
243
255
|
|
|
@@ -246,12 +258,58 @@ export class EdgeHttpClient {
|
|
|
246
258
|
//
|
|
247
259
|
|
|
248
260
|
public async uploadFunction(
|
|
261
|
+
ctx: Context,
|
|
249
262
|
pathParts: { functionId?: string },
|
|
250
263
|
body: UploadFunctionRequest,
|
|
251
|
-
args?:
|
|
264
|
+
args?: EdgeHttpCallArgs,
|
|
252
265
|
): Promise<UploadFunctionResponseBody> {
|
|
266
|
+
const formData = new FormData();
|
|
267
|
+
formData.append('name', body.name ?? '');
|
|
268
|
+
formData.append('version', body.version);
|
|
269
|
+
formData.append('ownerPublicKey', body.ownerPublicKey);
|
|
270
|
+
formData.append('entryPoint', body.entryPoint);
|
|
271
|
+
body.runtime && formData.append('runtime', body.runtime);
|
|
272
|
+
for (const [filename, content] of Object.entries(body.assets)) {
|
|
273
|
+
formData.append(
|
|
274
|
+
'assets',
|
|
275
|
+
new Blob([content as Uint8Array<ArrayBuffer>], { type: getFileMimeType(filename) }),
|
|
276
|
+
filename,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
253
279
|
const path = ['functions', ...(pathParts.functionId ? [pathParts.functionId] : [])].join('/');
|
|
254
|
-
return this._call(new URL(path, this.baseUrl), { ...args, body, method: 'PUT' });
|
|
280
|
+
return this._call(ctx, new URL(path, this.baseUrl), { ...args, body: formData, method: 'PUT', json: false });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
public async listFunctions(ctx: Context, args?: EdgeHttpCallArgs): Promise<any> {
|
|
284
|
+
return this._call(ctx, new URL('/functions', this.baseUrl), { ...args, method: 'GET' });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
public async invokeFunction(
|
|
288
|
+
ctx: Context,
|
|
289
|
+
params: {
|
|
290
|
+
functionId: string;
|
|
291
|
+
version?: string;
|
|
292
|
+
spaceId?: SpaceId;
|
|
293
|
+
cpuTimeLimit?: number;
|
|
294
|
+
subrequestsLimit?: number;
|
|
295
|
+
},
|
|
296
|
+
input: unknown,
|
|
297
|
+
args?: EdgeHttpCallArgs,
|
|
298
|
+
): Promise<any> {
|
|
299
|
+
const url = new URL(`/functions/${params.functionId}`, this.baseUrl);
|
|
300
|
+
if (params.version) {
|
|
301
|
+
url.searchParams.set('version', params.version);
|
|
302
|
+
}
|
|
303
|
+
if (params.spaceId) {
|
|
304
|
+
url.searchParams.set('spaceId', params.spaceId.toString());
|
|
305
|
+
}
|
|
306
|
+
if (params.cpuTimeLimit) {
|
|
307
|
+
url.searchParams.set('cpuTimeLimit', params.cpuTimeLimit.toString());
|
|
308
|
+
}
|
|
309
|
+
if (params.subrequestsLimit) {
|
|
310
|
+
url.searchParams.set('subrequestsLimit', params.subrequestsLimit.toString());
|
|
311
|
+
}
|
|
312
|
+
return this._call(ctx, url, { ...args, body: input, method: 'POST' });
|
|
255
313
|
}
|
|
256
314
|
|
|
257
315
|
//
|
|
@@ -259,12 +317,13 @@ export class EdgeHttpClient {
|
|
|
259
317
|
//
|
|
260
318
|
|
|
261
319
|
public async executeWorkflow(
|
|
320
|
+
ctx: Context,
|
|
262
321
|
spaceId: SpaceId,
|
|
263
322
|
graphId: ObjectId,
|
|
264
323
|
input: any,
|
|
265
|
-
args?:
|
|
324
|
+
args?: EdgeHttpCallArgs,
|
|
266
325
|
): Promise<ExecuteWorkflowResponseBody> {
|
|
267
|
-
return this._call(new URL(`/workflows/${spaceId}/${graphId}`, this.baseUrl), {
|
|
326
|
+
return this._call(ctx, new URL(`/workflows/${spaceId}/${graphId}`, this.baseUrl), {
|
|
268
327
|
...args,
|
|
269
328
|
body: input,
|
|
270
329
|
method: 'POST',
|
|
@@ -272,109 +331,172 @@ export class EdgeHttpClient {
|
|
|
272
331
|
}
|
|
273
332
|
|
|
274
333
|
//
|
|
275
|
-
//
|
|
334
|
+
// Triggers
|
|
276
335
|
//
|
|
277
336
|
|
|
278
|
-
|
|
279
|
-
return
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
withRetryConfig,
|
|
283
|
-
Effect.provide(FetchHttpClient.layer),
|
|
284
|
-
Effect.provide(HttpConfig.default),
|
|
285
|
-
Effect.withSpan('EdgeHttpClient'),
|
|
286
|
-
Effect.runPromise,
|
|
287
|
-
) as T;
|
|
337
|
+
public async getCronTriggers(ctx: Context, spaceId: SpaceId): Promise<GetCronTriggersResponse> {
|
|
338
|
+
return this._call<GetCronTriggersResponse>(ctx, new URL(`/functions/${spaceId}/triggers/crons`, this.baseUrl), {
|
|
339
|
+
method: 'GET',
|
|
340
|
+
});
|
|
288
341
|
}
|
|
289
342
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
343
|
+
public async getTriggersDispatcherStatus(
|
|
344
|
+
ctx: Context,
|
|
345
|
+
spaceId: SpaceId,
|
|
346
|
+
args?: EdgeHttpCallArgs,
|
|
347
|
+
): Promise<TriggersDispatcherStatus> {
|
|
348
|
+
return this._call<TriggersDispatcherStatus>(ctx, new URL(`/triggers/${spaceId}/status`, this.baseUrl), {
|
|
349
|
+
...args,
|
|
350
|
+
method: 'GET',
|
|
351
|
+
auth: true,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
public async forceRunCronTrigger(ctx: Context, spaceId: SpaceId, triggerId: ObjectId) {
|
|
356
|
+
return this._call(ctx, new URL(`/functions/${spaceId}/triggers/crons/${triggerId}/run`, this.baseUrl), {
|
|
357
|
+
method: 'POST',
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
//
|
|
362
|
+
// Query
|
|
363
|
+
//
|
|
364
|
+
|
|
365
|
+
public async execQuery(
|
|
366
|
+
ctx: Context,
|
|
367
|
+
spaceId: SpaceId,
|
|
368
|
+
body: QueryRequestProto,
|
|
369
|
+
args?: EdgeHttpCallArgs,
|
|
370
|
+
): Promise<QueryResponseProto> {
|
|
371
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/exec-query`, this.baseUrl), { ...args, body, method: 'POST' });
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
//
|
|
375
|
+
// Registry
|
|
376
|
+
//
|
|
377
|
+
|
|
378
|
+
public async getRegistryPlugins(ctx: Context, args?: EdgeHttpCallArgs): Promise<GetPluginsResponseBody> {
|
|
379
|
+
return this._call(ctx, new URL('/registry/plugins', this.baseUrl), { ...args, method: 'GET' });
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
public async getRegistryPluginVersions(
|
|
383
|
+
ctx: Context,
|
|
384
|
+
repo: string,
|
|
385
|
+
args?: EdgeHttpCallArgs,
|
|
386
|
+
): Promise<GetPluginVersionsResponseBody> {
|
|
387
|
+
return this._call(ctx, new URL(`/registry/plugins/${encodeURIComponent(repo)}/versions`, this.baseUrl), {
|
|
388
|
+
...args,
|
|
389
|
+
method: 'GET',
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
//
|
|
394
|
+
// Import/Export
|
|
395
|
+
//
|
|
396
|
+
|
|
397
|
+
public async importBundle(
|
|
398
|
+
ctx: Context,
|
|
399
|
+
spaceId: SpaceId,
|
|
400
|
+
body: ImportBundleRequest,
|
|
401
|
+
args?: EdgeHttpCallArgs,
|
|
402
|
+
): Promise<void> {
|
|
403
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/import`, this.baseUrl), { ...args, body, method: 'PUT' });
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
public async exportBundle(
|
|
407
|
+
ctx: Context,
|
|
408
|
+
spaceId: SpaceId,
|
|
409
|
+
body: ExportBundleRequest,
|
|
410
|
+
args?: EdgeHttpCallArgs,
|
|
411
|
+
): Promise<ExportBundleResponse> {
|
|
412
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/export`, this.baseUrl), { ...args, body, method: 'POST' });
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
//
|
|
416
|
+
// Proxy
|
|
417
|
+
//
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Fetch through the edge proxy for third-party REST APIs.
|
|
421
|
+
* TEMPORARY: currently routes through legacy open proxy. See https://github.com/dxos/edge/pull/576.
|
|
422
|
+
*/
|
|
423
|
+
public async proxyFetch(target: URL, init: RequestInit = {}): Promise<Response> {
|
|
424
|
+
return proxyFetchLegacy(target, init, this._clientTag);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
//
|
|
428
|
+
// AI service.
|
|
429
|
+
//
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Issue an authenticated request to the EDGE AI route (`/ai/generate/anthropic/*`), which
|
|
433
|
+
* proxies to the AI service. Used as the backend HTTP client for the Anthropic AI provider
|
|
434
|
+
* (see {@link EdgeAiHttpClient}).
|
|
435
|
+
*
|
|
436
|
+
* Returns the raw `Response` so streaming bodies are forwarded unchanged to `@effect/ai`.
|
|
437
|
+
* Requires an identity to have been set via {@link setIdentity}.
|
|
438
|
+
*/
|
|
439
|
+
// TODO(mykola): Merge into `BaseHttpClient._call` once it can return a streaming/raw `Response`;
|
|
440
|
+
// the auth/retry loop below duplicates the one in `_call`.
|
|
441
|
+
public async anthropicAiRequest(request: Request): Promise<Response> {
|
|
442
|
+
const incoming = new URL(request.url);
|
|
443
|
+
const base = this.baseUrl.replace(/\/$/, '');
|
|
444
|
+
const target = new URL(`${base}/ai/generate/anthropic${incoming.pathname}${incoming.search}`);
|
|
445
|
+
|
|
446
|
+
const method = request.method;
|
|
447
|
+
const body = method === 'GET' || method === 'HEAD' ? undefined : await request.arrayBuffer();
|
|
295
448
|
|
|
296
449
|
let handledAuth = false;
|
|
297
450
|
while (true) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const response = await fetch(url, request);
|
|
303
|
-
retryAfterHeaderValue = Number(response.headers.get('Retry-After'));
|
|
304
|
-
if (response.ok) {
|
|
305
|
-
const body = (await response.json()) as EdgeHttpResponse<T>;
|
|
306
|
-
if (body.success) {
|
|
307
|
-
return body.data;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
log.warn('unsuccessful edge response', { url, body });
|
|
311
|
-
if (body.errorData?.type === 'auth_challenge' && typeof body.errorData?.challenge === 'string') {
|
|
312
|
-
processingError = new EdgeAuthChallengeError(body.errorData.challenge, body.errorData);
|
|
313
|
-
} else {
|
|
314
|
-
processingError = EdgeCallFailedError.fromUnsuccessfulResponse(response, body);
|
|
315
|
-
}
|
|
316
|
-
} else if (response.status === 401 && !handledAuth) {
|
|
317
|
-
this._authHeader = await this._handleUnauthorized(response);
|
|
318
|
-
handledAuth = true;
|
|
319
|
-
continue;
|
|
320
|
-
} else {
|
|
321
|
-
processingError = EdgeCallFailedError.fromHttpFailure(response);
|
|
451
|
+
if (!this._authHeader) {
|
|
452
|
+
const authResponse = await fetch(new URL('/auth', this.baseUrl));
|
|
453
|
+
if (authResponse.status === 401) {
|
|
454
|
+
this._authHeader = await this._handleUnauthorized(authResponse);
|
|
322
455
|
}
|
|
323
|
-
} catch (error: any) {
|
|
324
|
-
processingError = EdgeCallFailedError.fromProcessingFailureCause(error);
|
|
325
456
|
}
|
|
326
457
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
458
|
+
const headers = new Headers(request.headers);
|
|
459
|
+
if (this._authHeader) {
|
|
460
|
+
headers.set('Authorization', this._authHeader);
|
|
461
|
+
}
|
|
462
|
+
if (this._clientTag) {
|
|
463
|
+
headers.set(EDGE_CLIENT_TAG_HEADER, this._clientTag);
|
|
331
464
|
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
465
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
466
|
+
const response = await fetch(target, { method, headers, body, signal: request.signal });
|
|
467
|
+
// Only retry edge auth when the 401 came from edge's own auth layer. Edge always sets
|
|
468
|
+
// `WWW-Authenticate` on its own 401s; upstream-forwarded 401s (e.g. invalid BYOK rejected
|
|
469
|
+
// by Anthropic) lack it and must be surfaced verbatim.
|
|
470
|
+
if (response.status === 401 && response.headers.get('WWW-Authenticate') !== null && !handledAuth) {
|
|
471
|
+
this._authHeader = await this._handleUnauthorized(response);
|
|
472
|
+
handledAuth = true;
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
340
475
|
|
|
341
|
-
|
|
342
|
-
|
|
476
|
+
return response;
|
|
477
|
+
}
|
|
343
478
|
}
|
|
344
|
-
}
|
|
345
479
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
body: body && JSON.stringify(body),
|
|
350
|
-
headers: authHeader ? { Authorization: authHeader } : undefined,
|
|
351
|
-
};
|
|
352
|
-
};
|
|
480
|
+
//
|
|
481
|
+
// Internal (Effect-based, used by tests)
|
|
482
|
+
//
|
|
353
483
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
484
|
+
public async _fetch<T>(url: URL, _args: { method: string }): Promise<T> {
|
|
485
|
+
return Function.pipe(
|
|
486
|
+
HttpClient.execute(HttpClientRequest.make(_args.method as any)(url.toString())),
|
|
487
|
+
withLogging,
|
|
488
|
+
withRetryConfig,
|
|
489
|
+
Effect.provide(FetchHttpClient.layer),
|
|
490
|
+
Effect.provide(HttpConfig.default),
|
|
491
|
+
Effect.withSpan('EdgeHttpClient'),
|
|
492
|
+
EffectEx.runAndForwardErrors,
|
|
493
|
+
) as T;
|
|
360
494
|
}
|
|
495
|
+
}
|
|
361
496
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
return false;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
if (retryAfter) {
|
|
372
|
-
await sleep(retryAfter);
|
|
373
|
-
} else {
|
|
374
|
-
const timeout = baseTimeout + Math.random() * jitter;
|
|
375
|
-
await sleep(timeout);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return true;
|
|
379
|
-
};
|
|
380
|
-
};
|
|
497
|
+
const getFileMimeType = (filename: string) =>
|
|
498
|
+
['.js', '.mjs'].some((ext) => filename.endsWith(ext))
|
|
499
|
+
? 'application/javascript+module'
|
|
500
|
+
: filename.endsWith('.wasm')
|
|
501
|
+
? 'application/wasm'
|
|
502
|
+
: 'application/octet-stream';
|