@dxos/edge-client 0.8.4-main.fffef41 → 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-JTBFRYNM.mjs → neutral/chunk-L5ZHLJ4B.mjs} +54 -47
- 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/{browser → neutral}/index.mjs +553 -467
- 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 +6 -3
- package/dist/types/src/edge-client.d.ts.map +1 -1
- package/dist/types/src/edge-http-client.d.ts +76 -75
- 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 +1 -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 +2 -2
- 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 +4 -0
- 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 +2 -2
- 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 +16 -11
- package/src/edge-client.ts +37 -7
- package/src/edge-http-client.test.ts +36 -2
- package/src/edge-http-client.ts +237 -270
- package/src/edge-ws-connection.ts +2 -1
- package/src/edge-ws-muxer.ts +49 -5
- package/src/http-client.test.ts +3 -2
- package/src/hub-http-client.ts +118 -0
- package/src/index.ts +4 -0
- package/src/testing/test-utils.ts +4 -4
- package/dist/lib/browser/chunk-VESGVCLQ.mjs +0 -301
- package/dist/lib/browser/chunk-VESGVCLQ.mjs.map +0 -7
- 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-JTBFRYNM.mjs.map +0 -7
- package/dist/lib/node-esm/edge-ws-muxer.mjs +0 -12
- package/dist/lib/node-esm/index.mjs +0 -1363
- 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
|
@@ -4,27 +4,32 @@
|
|
|
4
4
|
|
|
5
5
|
import * as FetchHttpClient from '@effect/platform/FetchHttpClient';
|
|
6
6
|
import * as HttpClient from '@effect/platform/HttpClient';
|
|
7
|
+
import * as HttpClientRequest from '@effect/platform/HttpClientRequest';
|
|
7
8
|
import * as Effect from 'effect/Effect';
|
|
8
9
|
import * as Function from 'effect/Function';
|
|
9
10
|
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
11
|
+
import { type Context } from '@dxos/context';
|
|
12
|
+
import { EffectEx } from '@dxos/effect';
|
|
12
13
|
import { invariant } from '@dxos/invariant';
|
|
13
14
|
import { type PublicKey, type SpaceId } from '@dxos/keys';
|
|
14
15
|
import { log } from '@dxos/log';
|
|
15
16
|
import {
|
|
17
|
+
type CompleteOAuthRegistrationRequest,
|
|
18
|
+
type CompleteOAuthRegistrationResponse,
|
|
16
19
|
type CreateAgentRequestBody,
|
|
17
20
|
type CreateAgentResponseBody,
|
|
18
21
|
type CreateSpaceRequest,
|
|
19
22
|
type CreateSpaceResponseBody,
|
|
20
|
-
|
|
21
|
-
EdgeCallFailedError,
|
|
23
|
+
EDGE_CLIENT_TAG_HEADER,
|
|
22
24
|
type EdgeStatus,
|
|
23
25
|
type ExecuteWorkflowResponseBody,
|
|
24
26
|
type ExportBundleRequest,
|
|
25
27
|
type ExportBundleResponse,
|
|
28
|
+
type FeedProtocol,
|
|
26
29
|
type GetAgentStatusResponseBody,
|
|
27
30
|
type GetNotarizationResponseBody,
|
|
31
|
+
type GetPluginVersionsResponseBody,
|
|
32
|
+
type GetPluginsResponseBody,
|
|
28
33
|
type ImportBundleRequest,
|
|
29
34
|
type InitiateOAuthFlowRequest,
|
|
30
35
|
type InitiateOAuthFlowResponse,
|
|
@@ -32,113 +37,85 @@ import {
|
|
|
32
37
|
type JoinSpaceResponseBody,
|
|
33
38
|
type ObjectId,
|
|
34
39
|
type PostNotarizationRequestBody,
|
|
35
|
-
type QueryResult,
|
|
36
|
-
type QueueQuery,
|
|
37
40
|
type RecoverIdentityRequest,
|
|
38
41
|
type RecoverIdentityResponseBody,
|
|
39
42
|
type UploadFunctionRequest,
|
|
40
43
|
type UploadFunctionResponseBody,
|
|
41
44
|
} from '@dxos/protocols';
|
|
45
|
+
import {
|
|
46
|
+
type QueryRequest as QueryRequestProto,
|
|
47
|
+
type QueryResponse as QueryResponseProto,
|
|
48
|
+
} from '@dxos/protocols/proto/dxos/echo/query';
|
|
42
49
|
import { createUrl } from '@dxos/util';
|
|
43
50
|
|
|
44
|
-
import { type
|
|
45
|
-
import {
|
|
46
|
-
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';
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
const DEFAULT_RETRY_JITTER = 500;
|
|
50
|
-
const DEFAULT_MAX_RETRIES_COUNT = 3;
|
|
51
|
-
const WARNING_BODY_SIZE = 10 * 1024 * 1024; // 10MB
|
|
55
|
+
export type { EdgeHttpCallArgs, RetryConfig } from './base-http-client';
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
*/
|
|
61
|
-
timeout?: number;
|
|
62
|
-
/**
|
|
63
|
-
* A random amount of time before retrying to help prevent large bursts of requests.
|
|
64
|
-
*/
|
|
65
|
-
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;
|
|
66
64
|
};
|
|
67
65
|
|
|
68
|
-
type
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
*/
|
|
76
|
-
json?: boolean;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Do not expect a standard EDGE JSON response with a `success` field.
|
|
80
|
-
* @deprecated Use only for debugging.
|
|
81
|
-
*/
|
|
82
|
-
rawResponse?: boolean;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Force authentication.
|
|
86
|
-
* This should be used for requests with large bodies to avoid sending the body twice.
|
|
87
|
-
* The client will call /auth endpoint to generate the auth header.
|
|
88
|
-
*/
|
|
89
|
-
auth?: boolean;
|
|
66
|
+
export type TriggersDispatcherStatus = {
|
|
67
|
+
isActive: boolean;
|
|
68
|
+
nextCronTaskRunTimestamp?: number;
|
|
69
|
+
registeredTriggers: string[];
|
|
70
|
+
stopAfterTimestamp?: number;
|
|
71
|
+
remainingMs?: number;
|
|
72
|
+
nextAlarmTimestamp?: number;
|
|
90
73
|
};
|
|
91
74
|
|
|
92
|
-
export type
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
export class EdgeHttpClient {
|
|
96
|
-
private readonly _baseUrl: string;
|
|
97
|
-
|
|
98
|
-
private _edgeIdentity: EdgeIdentity | undefined;
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Auth header is cached until receiving the next 401 from EDGE, at which point it gets refreshed.
|
|
102
|
-
*/
|
|
103
|
-
private _authHeader: string | undefined;
|
|
104
|
-
|
|
105
|
-
constructor(baseUrl: string) {
|
|
106
|
-
this._baseUrl = getEdgeUrlWithProtocol(baseUrl, 'http');
|
|
107
|
-
log('created', { url: this._baseUrl });
|
|
108
|
-
}
|
|
75
|
+
export type GetCronTriggersResponse = {
|
|
76
|
+
cronIds: string[];
|
|
77
|
+
};
|
|
109
78
|
|
|
110
|
-
|
|
111
|
-
return this._baseUrl;
|
|
112
|
-
}
|
|
79
|
+
export type EdgeHttpClientOptions = BaseHttpClientOptions;
|
|
113
80
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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 });
|
|
119
91
|
}
|
|
120
92
|
|
|
121
93
|
//
|
|
122
94
|
// Status
|
|
123
95
|
//
|
|
124
96
|
|
|
125
|
-
public async getStatus(args?:
|
|
126
|
-
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 });
|
|
127
99
|
}
|
|
128
100
|
|
|
129
101
|
//
|
|
130
102
|
// Agents
|
|
131
103
|
//
|
|
132
104
|
|
|
133
|
-
public createAgent(
|
|
134
|
-
|
|
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 });
|
|
135
111
|
}
|
|
136
112
|
|
|
137
113
|
public getAgentStatus(
|
|
114
|
+
ctx: Context,
|
|
138
115
|
request: { ownerIdentityKey: PublicKey },
|
|
139
|
-
args?:
|
|
116
|
+
args?: EdgeHttpCallArgs,
|
|
140
117
|
): Promise<GetAgentStatusResponseBody> {
|
|
141
|
-
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), {
|
|
142
119
|
...args,
|
|
143
120
|
method: 'GET',
|
|
144
121
|
});
|
|
@@ -148,16 +125,21 @@ export class EdgeHttpClient {
|
|
|
148
125
|
// Credentials
|
|
149
126
|
//
|
|
150
127
|
|
|
151
|
-
public getCredentialsForNotarization(
|
|
152
|
-
|
|
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' });
|
|
153
134
|
}
|
|
154
135
|
|
|
155
136
|
public async notarizeCredentials(
|
|
137
|
+
ctx: Context,
|
|
156
138
|
spaceId: SpaceId,
|
|
157
139
|
body: PostNotarizationRequestBody,
|
|
158
|
-
args?:
|
|
140
|
+
args?: EdgeHttpCallArgs,
|
|
159
141
|
): Promise<void> {
|
|
160
|
-
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' });
|
|
161
143
|
}
|
|
162
144
|
|
|
163
145
|
//
|
|
@@ -165,41 +147,52 @@ export class EdgeHttpClient {
|
|
|
165
147
|
//
|
|
166
148
|
|
|
167
149
|
public async recoverIdentity(
|
|
150
|
+
ctx: Context,
|
|
168
151
|
body: RecoverIdentityRequest,
|
|
169
|
-
args?:
|
|
152
|
+
args?: EdgeHttpCallArgs,
|
|
170
153
|
): Promise<RecoverIdentityResponseBody> {
|
|
171
|
-
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' });
|
|
172
155
|
}
|
|
173
156
|
|
|
174
157
|
//
|
|
175
|
-
// Invitations
|
|
158
|
+
// Invitations (space join)
|
|
176
159
|
//
|
|
177
160
|
|
|
178
161
|
public async joinSpaceByInvitation(
|
|
162
|
+
ctx: Context,
|
|
179
163
|
spaceId: SpaceId,
|
|
180
164
|
body: JoinSpaceRequest,
|
|
181
|
-
args?:
|
|
165
|
+
args?: EdgeHttpCallArgs,
|
|
182
166
|
): Promise<JoinSpaceResponseBody> {
|
|
183
|
-
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' });
|
|
184
168
|
}
|
|
185
169
|
|
|
186
170
|
//
|
|
187
|
-
// OAuth
|
|
171
|
+
// OAuth
|
|
188
172
|
//
|
|
189
173
|
|
|
190
174
|
public async initiateOAuthFlow(
|
|
175
|
+
ctx: Context,
|
|
191
176
|
body: InitiateOAuthFlowRequest,
|
|
192
|
-
args?:
|
|
177
|
+
args?: EdgeHttpCallArgs,
|
|
193
178
|
): Promise<InitiateOAuthFlowResponse> {
|
|
194
|
-
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' });
|
|
195
188
|
}
|
|
196
189
|
|
|
197
190
|
//
|
|
198
191
|
// Spaces
|
|
199
192
|
//
|
|
200
193
|
|
|
201
|
-
async createSpace(body: CreateSpaceRequest, args?:
|
|
202
|
-
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' });
|
|
203
196
|
}
|
|
204
197
|
|
|
205
198
|
//
|
|
@@ -207,13 +200,16 @@ export class EdgeHttpClient {
|
|
|
207
200
|
//
|
|
208
201
|
|
|
209
202
|
public async queryQueue(
|
|
203
|
+
ctx: Context,
|
|
210
204
|
subspaceTag: string,
|
|
211
205
|
spaceId: SpaceId,
|
|
212
|
-
query: QueueQuery,
|
|
213
|
-
args?:
|
|
214
|
-
): Promise<
|
|
215
|
-
const
|
|
206
|
+
query: FeedProtocol.QueueQuery,
|
|
207
|
+
args?: EdgeHttpCallArgs,
|
|
208
|
+
): Promise<EdgeQueryQueueResponse> {
|
|
209
|
+
const queueId = query.queueIds?.[0];
|
|
210
|
+
invariant(queueId, 'queueId required');
|
|
216
211
|
return this._call(
|
|
212
|
+
ctx,
|
|
217
213
|
createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}/query`, this.baseUrl), {
|
|
218
214
|
after: query.after,
|
|
219
215
|
before: query.before,
|
|
@@ -221,21 +217,19 @@ export class EdgeHttpClient {
|
|
|
221
217
|
reverse: query.reverse,
|
|
222
218
|
objectIds: query.objectIds?.join(','),
|
|
223
219
|
}),
|
|
224
|
-
{
|
|
225
|
-
...args,
|
|
226
|
-
method: 'GET',
|
|
227
|
-
},
|
|
220
|
+
{ ...args, method: 'GET' },
|
|
228
221
|
);
|
|
229
222
|
}
|
|
230
223
|
|
|
231
224
|
public async insertIntoQueue(
|
|
225
|
+
ctx: Context,
|
|
232
226
|
subspaceTag: string,
|
|
233
227
|
spaceId: SpaceId,
|
|
234
228
|
queueId: ObjectId,
|
|
235
229
|
objects: unknown[],
|
|
236
|
-
args?:
|
|
230
|
+
args?: EdgeHttpCallArgs,
|
|
237
231
|
): Promise<void> {
|
|
238
|
-
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), {
|
|
239
233
|
...args,
|
|
240
234
|
body: { objects },
|
|
241
235
|
method: 'POST',
|
|
@@ -243,20 +237,19 @@ export class EdgeHttpClient {
|
|
|
243
237
|
}
|
|
244
238
|
|
|
245
239
|
public async deleteFromQueue(
|
|
240
|
+
ctx: Context,
|
|
246
241
|
subspaceTag: string,
|
|
247
242
|
spaceId: SpaceId,
|
|
248
243
|
queueId: ObjectId,
|
|
249
244
|
objectIds: ObjectId[],
|
|
250
|
-
args?:
|
|
245
|
+
args?: EdgeHttpCallArgs,
|
|
251
246
|
): Promise<void> {
|
|
252
247
|
return this._call(
|
|
248
|
+
ctx,
|
|
253
249
|
createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
|
|
254
250
|
ids: objectIds.join(','),
|
|
255
251
|
}),
|
|
256
|
-
{
|
|
257
|
-
...args,
|
|
258
|
-
method: 'DELETE',
|
|
259
|
-
},
|
|
252
|
+
{ ...args, method: 'DELETE' },
|
|
260
253
|
);
|
|
261
254
|
}
|
|
262
255
|
|
|
@@ -265,9 +258,10 @@ export class EdgeHttpClient {
|
|
|
265
258
|
//
|
|
266
259
|
|
|
267
260
|
public async uploadFunction(
|
|
261
|
+
ctx: Context,
|
|
268
262
|
pathParts: { functionId?: string },
|
|
269
263
|
body: UploadFunctionRequest,
|
|
270
|
-
args?:
|
|
264
|
+
args?: EdgeHttpCallArgs,
|
|
271
265
|
): Promise<UploadFunctionResponseBody> {
|
|
272
266
|
const formData = new FormData();
|
|
273
267
|
formData.append('name', body.name ?? '');
|
|
@@ -282,21 +276,16 @@ export class EdgeHttpClient {
|
|
|
282
276
|
filename,
|
|
283
277
|
);
|
|
284
278
|
}
|
|
285
|
-
|
|
286
279
|
const path = ['functions', ...(pathParts.functionId ? [pathParts.functionId] : [])].join('/');
|
|
287
|
-
return this._call(new URL(path, this.baseUrl), {
|
|
288
|
-
...args,
|
|
289
|
-
body: formData,
|
|
290
|
-
method: 'PUT',
|
|
291
|
-
json: false,
|
|
292
|
-
});
|
|
280
|
+
return this._call(ctx, new URL(path, this.baseUrl), { ...args, body: formData, method: 'PUT', json: false });
|
|
293
281
|
}
|
|
294
282
|
|
|
295
|
-
public async listFunctions(args?:
|
|
296
|
-
return this._call(new URL('/functions', this.baseUrl), { ...args, method: 'GET' });
|
|
283
|
+
public async listFunctions(ctx: Context, args?: EdgeHttpCallArgs): Promise<any> {
|
|
284
|
+
return this._call(ctx, new URL('/functions', this.baseUrl), { ...args, method: 'GET' });
|
|
297
285
|
}
|
|
298
286
|
|
|
299
287
|
public async invokeFunction(
|
|
288
|
+
ctx: Context,
|
|
300
289
|
params: {
|
|
301
290
|
functionId: string;
|
|
302
291
|
version?: string;
|
|
@@ -305,7 +294,7 @@ export class EdgeHttpClient {
|
|
|
305
294
|
subrequestsLimit?: number;
|
|
306
295
|
},
|
|
307
296
|
input: unknown,
|
|
308
|
-
args?:
|
|
297
|
+
args?: EdgeHttpCallArgs,
|
|
309
298
|
): Promise<any> {
|
|
310
299
|
const url = new URL(`/functions/${params.functionId}`, this.baseUrl);
|
|
311
300
|
if (params.version) {
|
|
@@ -320,13 +309,7 @@ export class EdgeHttpClient {
|
|
|
320
309
|
if (params.subrequestsLimit) {
|
|
321
310
|
url.searchParams.set('subrequestsLimit', params.subrequestsLimit.toString());
|
|
322
311
|
}
|
|
323
|
-
|
|
324
|
-
return this._call(url, {
|
|
325
|
-
...args,
|
|
326
|
-
body: input,
|
|
327
|
-
method: 'POST',
|
|
328
|
-
rawResponse: true,
|
|
329
|
-
});
|
|
312
|
+
return this._call(ctx, url, { ...args, body: input, method: 'POST' });
|
|
330
313
|
}
|
|
331
314
|
|
|
332
315
|
//
|
|
@@ -334,12 +317,13 @@ export class EdgeHttpClient {
|
|
|
334
317
|
//
|
|
335
318
|
|
|
336
319
|
public async executeWorkflow(
|
|
320
|
+
ctx: Context,
|
|
337
321
|
spaceId: SpaceId,
|
|
338
322
|
graphId: ObjectId,
|
|
339
323
|
input: any,
|
|
340
|
-
args?:
|
|
324
|
+
args?: EdgeHttpCallArgs,
|
|
341
325
|
): Promise<ExecuteWorkflowResponseBody> {
|
|
342
|
-
return this._call(new URL(`/workflows/${spaceId}/${graphId}`, this.baseUrl), {
|
|
326
|
+
return this._call(ctx, new URL(`/workflows/${spaceId}/${graphId}`, this.baseUrl), {
|
|
343
327
|
...args,
|
|
344
328
|
body: input,
|
|
345
329
|
method: 'POST',
|
|
@@ -350,185 +334,168 @@ export class EdgeHttpClient {
|
|
|
350
334
|
// Triggers
|
|
351
335
|
//
|
|
352
336
|
|
|
353
|
-
public async getCronTriggers(spaceId: SpaceId) {
|
|
354
|
-
return this._call(new URL(`/
|
|
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
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
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
|
+
});
|
|
355
359
|
}
|
|
356
360
|
|
|
357
361
|
//
|
|
358
|
-
//
|
|
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
|
|
359
395
|
//
|
|
360
396
|
|
|
361
397
|
public async importBundle(
|
|
362
|
-
|
|
398
|
+
ctx: Context,
|
|
399
|
+
spaceId: SpaceId,
|
|
363
400
|
body: ImportBundleRequest,
|
|
364
|
-
args?:
|
|
401
|
+
args?: EdgeHttpCallArgs,
|
|
365
402
|
): Promise<void> {
|
|
366
|
-
return this._call(new URL(`/spaces/${spaceId}/import`, this.baseUrl), { ...args, body, method: 'PUT' });
|
|
403
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/import`, this.baseUrl), { ...args, body, method: 'PUT' });
|
|
367
404
|
}
|
|
368
405
|
|
|
369
406
|
public async exportBundle(
|
|
407
|
+
ctx: Context,
|
|
370
408
|
spaceId: SpaceId,
|
|
371
409
|
body: ExportBundleRequest,
|
|
372
|
-
args?:
|
|
410
|
+
args?: EdgeHttpCallArgs,
|
|
373
411
|
): Promise<ExportBundleResponse> {
|
|
374
|
-
return this._call(new URL(`/spaces/${spaceId}/export`, this.baseUrl), {
|
|
375
|
-
...args,
|
|
376
|
-
body,
|
|
377
|
-
method: 'POST',
|
|
378
|
-
});
|
|
412
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/export`, this.baseUrl), { ...args, body, method: 'POST' });
|
|
379
413
|
}
|
|
380
414
|
|
|
381
415
|
//
|
|
382
|
-
//
|
|
416
|
+
// Proxy
|
|
383
417
|
//
|
|
384
418
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
Effect.provide(HttpConfig.default),
|
|
392
|
-
Effect.withSpan('EdgeHttpClient'),
|
|
393
|
-
Effect.runPromise,
|
|
394
|
-
) as T;
|
|
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);
|
|
395
425
|
}
|
|
396
426
|
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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();
|
|
402
448
|
|
|
403
449
|
let handledAuth = false;
|
|
404
|
-
const tryCount = 1;
|
|
405
450
|
while (true) {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
if (response.status === 401) {
|
|
411
|
-
this._authHeader = await this._handleUnauthorized(response);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const request = createRequest(args, this._authHeader);
|
|
416
|
-
log('call edge', { url, tryCount, authHeader: !!this._authHeader });
|
|
417
|
-
const response = await fetch(url, request);
|
|
418
|
-
|
|
419
|
-
if (response.ok) {
|
|
420
|
-
const body = await response.clone().json();
|
|
421
|
-
if (args.rawResponse) {
|
|
422
|
-
return body as any;
|
|
423
|
-
}
|
|
424
|
-
invariant(body, 'Expected body to be present');
|
|
425
|
-
if (!('success' in body)) {
|
|
426
|
-
return body;
|
|
427
|
-
}
|
|
428
|
-
if (body.success) {
|
|
429
|
-
return body.data;
|
|
430
|
-
}
|
|
431
|
-
} else if (response.status === 401 && !handledAuth) {
|
|
432
|
-
this._authHeader = await this._handleUnauthorized(response);
|
|
433
|
-
handledAuth = true;
|
|
434
|
-
continue;
|
|
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);
|
|
435
455
|
}
|
|
456
|
+
}
|
|
436
457
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
processingError = new EdgeAuthChallengeError(body.errorData.challenge, body.errorData);
|
|
444
|
-
} else if (body?.success === false) {
|
|
445
|
-
processingError = EdgeCallFailedError.fromUnsuccessfulResponse(response, body);
|
|
446
|
-
} else {
|
|
447
|
-
invariant(!response.ok, 'Expected response to not be ok.');
|
|
448
|
-
processingError = await EdgeCallFailedError.fromHttpFailure(response);
|
|
449
|
-
}
|
|
450
|
-
} catch (error: any) {
|
|
451
|
-
processingError = EdgeCallFailedError.fromProcessingFailureCause(error);
|
|
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);
|
|
452
464
|
}
|
|
453
465
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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;
|
|
458
474
|
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
475
|
|
|
462
|
-
|
|
463
|
-
if (!this._edgeIdentity) {
|
|
464
|
-
log.warn('unauthorized response received before identity was set');
|
|
465
|
-
throw await EdgeCallFailedError.fromHttpFailure(response);
|
|
476
|
+
return response;
|
|
466
477
|
}
|
|
467
|
-
|
|
468
|
-
const challenge = await handleAuthChallenge(response, this._edgeIdentity);
|
|
469
|
-
return encodeAuthHeader(challenge);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const createRequest = (
|
|
474
|
-
{ method, body, json = true }: EdgeHttpRequestArgs,
|
|
475
|
-
authHeader: string | undefined,
|
|
476
|
-
): RequestInit => {
|
|
477
|
-
let requestBody: BodyInit | undefined;
|
|
478
|
-
const headers: HeadersInit = {};
|
|
479
|
-
|
|
480
|
-
if (json) {
|
|
481
|
-
requestBody = body && JSON.stringify(body);
|
|
482
|
-
headers['Content-Type'] = 'application/json';
|
|
483
|
-
} else {
|
|
484
|
-
requestBody = body;
|
|
485
478
|
}
|
|
486
479
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (authHeader) {
|
|
492
|
-
headers['Authorization'] = authHeader;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
return {
|
|
496
|
-
method,
|
|
497
|
-
body: requestBody,
|
|
498
|
-
headers,
|
|
499
|
-
};
|
|
500
|
-
};
|
|
480
|
+
//
|
|
481
|
+
// Internal (Effect-based, used by tests)
|
|
482
|
+
//
|
|
501
483
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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;
|
|
508
494
|
}
|
|
509
|
-
|
|
510
|
-
let retries = 0;
|
|
511
|
-
const maxRetries = retry.count ?? DEFAULT_MAX_RETRIES_COUNT;
|
|
512
|
-
const baseTimeout = retry.timeout ?? DEFAULT_RETRY_TIMEOUT;
|
|
513
|
-
const jitter = retry.jitter ?? DEFAULT_RETRY_JITTER;
|
|
514
|
-
return async (ctx: Context, retryAfter?: number) => {
|
|
515
|
-
if (++retries > maxRetries || ctx.disposed) {
|
|
516
|
-
return false;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
if (retryAfter) {
|
|
520
|
-
await sleep(retryAfter);
|
|
521
|
-
} else {
|
|
522
|
-
const timeout = baseTimeout + Math.random() * jitter;
|
|
523
|
-
await sleep(timeout);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
return true;
|
|
527
|
-
};
|
|
528
|
-
};
|
|
495
|
+
}
|
|
529
496
|
|
|
530
497
|
const getFileMimeType = (filename: string) =>
|
|
531
|
-
['.js', '.mjs'].some((
|
|
498
|
+
['.js', '.mjs'].some((ext) => filename.endsWith(ext))
|
|
532
499
|
? 'application/javascript+module'
|
|
533
500
|
: filename.endsWith('.wasm')
|
|
534
501
|
? 'application/wasm'
|