@dxos/edge-client 0.8.4-main.fffef41 → 0.9.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.
- 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-client.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
scheduleMicroTask,
|
|
11
11
|
scheduleTaskInterval,
|
|
12
12
|
} from '@dxos/async';
|
|
13
|
+
import { Context, TRACE_SPAN_ATTRIBUTE, type TraceContextData } from '@dxos/context';
|
|
13
14
|
import { type Lifecycle, Resource } from '@dxos/context';
|
|
14
15
|
import { log, logInfo } from '@dxos/log';
|
|
15
16
|
import { type Message } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
|
|
@@ -35,6 +36,8 @@ export type MessengerConfig = {
|
|
|
35
36
|
timeout?: number;
|
|
36
37
|
protocol?: Protocol;
|
|
37
38
|
disableAuth?: boolean;
|
|
39
|
+
/** Sent as `X-DXOS-Client-Tag` on the WebSocket upgrade (Node/`ws` only; ignored in browsers). */
|
|
40
|
+
clientTag?: string;
|
|
38
41
|
};
|
|
39
42
|
|
|
40
43
|
export interface EdgeConnection extends Required<Lifecycle> {
|
|
@@ -45,7 +48,7 @@ export interface EdgeConnection extends Required<Lifecycle> {
|
|
|
45
48
|
get isOpen(): boolean;
|
|
46
49
|
get status(): EdgeStatus;
|
|
47
50
|
setIdentity(identity: EdgeIdentity): void;
|
|
48
|
-
send(message: Message): Promise<void>;
|
|
51
|
+
send(ctx: Context, message: Message): Promise<void>;
|
|
49
52
|
onMessage(listener: MessageListener): () => void;
|
|
50
53
|
onReconnected(listener: ReconnectListener): () => void;
|
|
51
54
|
}
|
|
@@ -126,7 +129,7 @@ export class EdgeClient extends Resource implements EdgeConnection {
|
|
|
126
129
|
* Send message.
|
|
127
130
|
* NOTE: The message is guaranteed to be delivered but the service must respond with a message to confirm processing.
|
|
128
131
|
*/
|
|
129
|
-
public async send(message: Message) {
|
|
132
|
+
public async send(ctx: Context, message: Message) {
|
|
130
133
|
if (this._ready.state !== TriggerState.RESOLVED) {
|
|
131
134
|
log('waiting for websocket');
|
|
132
135
|
await this._ready.wait({ timeout: this._config.timeout ?? DEFAULT_TIMEOUT });
|
|
@@ -143,6 +146,15 @@ export class EdgeClient extends Resource implements EdgeConnection {
|
|
|
143
146
|
throw new EdgeIdentityChangedError();
|
|
144
147
|
}
|
|
145
148
|
|
|
149
|
+
const traceCtx = ctx.getAttribute(TRACE_SPAN_ATTRIBUTE) as TraceContextData | undefined;
|
|
150
|
+
if (traceCtx) {
|
|
151
|
+
message.traceContext = {
|
|
152
|
+
$typeName: 'dxos.edge.messenger.TraceContext',
|
|
153
|
+
traceparent: traceCtx.traceparent,
|
|
154
|
+
tracestate: traceCtx.tracestate,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
146
158
|
this._currentConnection.send(message);
|
|
147
159
|
}
|
|
148
160
|
|
|
@@ -151,7 +163,7 @@ export class EdgeClient extends Resource implements EdgeConnection {
|
|
|
151
163
|
return () => this._messageListeners.delete(listener);
|
|
152
164
|
}
|
|
153
165
|
|
|
154
|
-
public onReconnected(listener:
|
|
166
|
+
public onReconnected(listener: ReconnectListener) {
|
|
155
167
|
this._reconnectListeners.add(listener);
|
|
156
168
|
if (this._ready.state === TriggerState.RESOLVED) {
|
|
157
169
|
// Microtask so that listener is always called asynchronously, no matter the state of the ready trigger
|
|
@@ -219,7 +231,11 @@ export class EdgeClient extends Resource implements EdgeConnection {
|
|
|
219
231
|
log('Opening websocket', { url: url.toString(), protocolHeader });
|
|
220
232
|
const connection = new EdgeWsConnection(
|
|
221
233
|
identity,
|
|
222
|
-
{
|
|
234
|
+
{
|
|
235
|
+
url,
|
|
236
|
+
protocolHeader,
|
|
237
|
+
headers: this._config.clientTag ? { 'X-DXOS-Client-Tag': this._config.clientTag } : undefined,
|
|
238
|
+
},
|
|
223
239
|
{
|
|
224
240
|
onConnected: () => {
|
|
225
241
|
if (this._isActive(connection)) {
|
|
@@ -253,9 +269,23 @@ export class EdgeClient extends Resource implements EdgeConnection {
|
|
|
253
269
|
this._currentConnection = connection;
|
|
254
270
|
|
|
255
271
|
await connection.open();
|
|
256
|
-
|
|
257
|
-
//
|
|
258
|
-
|
|
272
|
+
|
|
273
|
+
// The connection is only a successful start once the socket becomes ready. A socket that
|
|
274
|
+
// closes or errors before becoming ready (or never connects within the timeout) is a failed
|
|
275
|
+
// start: throwing lets PersistentLifecycle apply its backoff. Returning here would mark the
|
|
276
|
+
// attempt as successful and reset the backoff, degenerating reconnects into a hot loop when
|
|
277
|
+
// the server accepts then immediately drops the socket.
|
|
278
|
+
const becameReady = await Promise.race([
|
|
279
|
+
this._ready.wait({ timeout: this._config.timeout ?? DEFAULT_TIMEOUT }).then(
|
|
280
|
+
() => true,
|
|
281
|
+
() => false,
|
|
282
|
+
),
|
|
283
|
+
restartRequired.wait().then(() => false),
|
|
284
|
+
]);
|
|
285
|
+
if (!becameReady) {
|
|
286
|
+
throw new EdgeConnectionClosedError();
|
|
287
|
+
}
|
|
288
|
+
|
|
259
289
|
return connection;
|
|
260
290
|
}
|
|
261
291
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { describe, it } from 'vitest';
|
|
5
|
+
import { afterEach, describe, it, test, vi } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { createEphemeralEdgeIdentity } from './auth';
|
|
8
8
|
import { EdgeHttpClient } from './edge-http-client';
|
|
@@ -16,7 +16,41 @@ describe.skipIf(process.env.CI)('EdgeHttpClient', () => {
|
|
|
16
16
|
const identity = await createEphemeralEdgeIdentity();
|
|
17
17
|
client.setIdentity(identity);
|
|
18
18
|
|
|
19
|
-
const
|
|
19
|
+
const { Context } = await import('@dxos/context');
|
|
20
|
+
const result = await client.getStatus(Context.default());
|
|
20
21
|
expect(result).toBeDefined();
|
|
21
22
|
});
|
|
22
23
|
});
|
|
24
|
+
|
|
25
|
+
describe('EdgeHttpClient.anthropicAiRequest', () => {
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
vi.unstubAllGlobals();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('re-bases the request path onto the EDGE /ai/generate/anthropic route', async ({ expect }) => {
|
|
31
|
+
const fetchMock = vi.fn(async (input: any, _init?: RequestInit) => {
|
|
32
|
+
const url = String(input instanceof URL ? input : (input.url ?? input));
|
|
33
|
+
// `/auth` preflight: respond non-401 so no auth header is attached.
|
|
34
|
+
if (url.endsWith('/auth')) {
|
|
35
|
+
return new Response(null, { status: 200 });
|
|
36
|
+
}
|
|
37
|
+
return new Response(JSON.stringify({ ok: true }), { status: 200 });
|
|
38
|
+
});
|
|
39
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
40
|
+
|
|
41
|
+
const client = new EdgeHttpClient('https://edge.example.com');
|
|
42
|
+
const response = await client.anthropicAiRequest(
|
|
43
|
+
new Request('http://edge/v1/messages?beta=true', {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
body: JSON.stringify({ model: 'claude' }),
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(response.status).toBe(200);
|
|
50
|
+
|
|
51
|
+
const targetCall = fetchMock.mock.calls.find((call) => !String(call[0]).endsWith('/auth'));
|
|
52
|
+
expect(targetCall).toBeDefined();
|
|
53
|
+
expect(String(targetCall![0])).toBe('https://edge.example.com/ai/generate/anthropic/v1/messages?beta=true');
|
|
54
|
+
expect(targetCall![1]?.method).toBe('POST');
|
|
55
|
+
});
|
|
56
|
+
});
|