@antseed/node 0.1.0 → 0.1.2
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 +674 -0
- package/README.md +7 -5
- package/dist/discovery/http-metadata-resolver.d.ts +6 -0
- package/dist/discovery/http-metadata-resolver.d.ts.map +1 -1
- package/dist/discovery/http-metadata-resolver.js +32 -4
- package/dist/discovery/http-metadata-resolver.js.map +1 -1
- package/dist/discovery/peer-lookup.d.ts +1 -0
- package/dist/discovery/peer-lookup.d.ts.map +1 -1
- package/dist/discovery/peer-lookup.js +10 -25
- package/dist/discovery/peer-lookup.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/seller-provider.d.ts +13 -1
- package/dist/interfaces/seller-provider.d.ts.map +1 -1
- package/dist/node.d.ts +13 -3
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +146 -21
- package/dist/node.js.map +1 -1
- package/dist/proxy/proxy-mux.d.ts +3 -1
- package/dist/proxy/proxy-mux.d.ts.map +1 -1
- package/dist/proxy/proxy-mux.js +9 -5
- package/dist/proxy/proxy-mux.js.map +1 -1
- package/dist/types/http.d.ts +1 -0
- package/dist/types/http.d.ts.map +1 -1
- package/dist/types/http.js +1 -1
- package/dist/types/http.js.map +1 -1
- package/package.json +14 -10
- package/contracts/AntseedEscrow.sol +0 -310
- package/contracts/MockUSDC.sol +0 -64
- package/contracts/README.md +0 -102
- package/src/config/encryption.test.ts +0 -49
- package/src/config/encryption.ts +0 -53
- package/src/config/plugin-config-manager.test.ts +0 -92
- package/src/config/plugin-config-manager.ts +0 -153
- package/src/config/plugin-loader.ts +0 -90
- package/src/discovery/announcer.ts +0 -169
- package/src/discovery/bootstrap.ts +0 -57
- package/src/discovery/default-metadata-resolver.ts +0 -18
- package/src/discovery/dht-health.ts +0 -136
- package/src/discovery/dht-node.ts +0 -191
- package/src/discovery/http-metadata-resolver.ts +0 -47
- package/src/discovery/index.ts +0 -15
- package/src/discovery/metadata-codec.ts +0 -453
- package/src/discovery/metadata-resolver.ts +0 -7
- package/src/discovery/metadata-server.ts +0 -73
- package/src/discovery/metadata-validator.ts +0 -172
- package/src/discovery/peer-lookup.ts +0 -122
- package/src/discovery/peer-metadata.ts +0 -34
- package/src/discovery/peer-selector.ts +0 -134
- package/src/discovery/profile-manager.ts +0 -131
- package/src/discovery/profile-search.ts +0 -100
- package/src/discovery/reputation-verifier.ts +0 -54
- package/src/index.ts +0 -61
- package/src/interfaces/buyer-router.ts +0 -21
- package/src/interfaces/plugin.ts +0 -36
- package/src/interfaces/seller-provider.ts +0 -81
- package/src/metering/index.ts +0 -6
- package/src/metering/receipt-generator.ts +0 -105
- package/src/metering/receipt-verifier.ts +0 -102
- package/src/metering/session-tracker.ts +0 -145
- package/src/metering/storage.ts +0 -600
- package/src/metering/token-counter.ts +0 -127
- package/src/metering/usage-aggregator.ts +0 -236
- package/src/node.ts +0 -1698
- package/src/p2p/connection-auth.ts +0 -152
- package/src/p2p/connection-manager.ts +0 -916
- package/src/p2p/handshake.ts +0 -162
- package/src/p2p/ice-config.ts +0 -59
- package/src/p2p/identity.ts +0 -110
- package/src/p2p/index.ts +0 -11
- package/src/p2p/keepalive.ts +0 -118
- package/src/p2p/message-protocol.ts +0 -171
- package/src/p2p/nat-traversal.ts +0 -169
- package/src/p2p/payment-codec.ts +0 -165
- package/src/p2p/payment-mux.ts +0 -153
- package/src/p2p/reconnect.ts +0 -117
- package/src/payments/balance-manager.ts +0 -77
- package/src/payments/buyer-payment-manager.ts +0 -414
- package/src/payments/disputes.ts +0 -72
- package/src/payments/evm/escrow-client.ts +0 -263
- package/src/payments/evm/keypair.ts +0 -31
- package/src/payments/evm/signatures.ts +0 -103
- package/src/payments/evm/wallet.ts +0 -42
- package/src/payments/index.ts +0 -50
- package/src/payments/settlement.ts +0 -40
- package/src/payments/types.ts +0 -79
- package/src/proxy/index.ts +0 -3
- package/src/proxy/provider-detection.ts +0 -78
- package/src/proxy/proxy-mux.ts +0 -173
- package/src/proxy/request-codec.ts +0 -294
- package/src/reputation/index.ts +0 -6
- package/src/reputation/rating-manager.ts +0 -118
- package/src/reputation/report-manager.ts +0 -91
- package/src/reputation/trust-engine.ts +0 -120
- package/src/reputation/trust-score.ts +0 -74
- package/src/reputation/uptime-tracker.ts +0 -155
- package/src/routing/default-router.ts +0 -75
- package/src/types/bittorrent-dht.d.ts +0 -19
- package/src/types/buyer.ts +0 -37
- package/src/types/capability.ts +0 -34
- package/src/types/connection.ts +0 -29
- package/src/types/http.ts +0 -20
- package/src/types/index.ts +0 -14
- package/src/types/metering.ts +0 -175
- package/src/types/nat-api.d.ts +0 -29
- package/src/types/peer-profile.ts +0 -25
- package/src/types/peer.ts +0 -62
- package/src/types/plugin-config.ts +0 -31
- package/src/types/protocol.ts +0 -162
- package/src/types/provider.ts +0 -40
- package/src/types/rating.ts +0 -23
- package/src/types/report.ts +0 -30
- package/src/types/seller.ts +0 -38
- package/src/types/staking.ts +0 -23
- package/src/utils/debug.ts +0 -30
- package/src/utils/hex.ts +0 -14
- package/tests/balance-manager.test.ts +0 -156
- package/tests/bootstrap.test.ts +0 -108
- package/tests/buyer-payment-manager.test.ts +0 -358
- package/tests/connection-auth.test.ts +0 -87
- package/tests/default-router.test.ts +0 -148
- package/tests/evm-keypair.test.ts +0 -173
- package/tests/identity.test.ts +0 -133
- package/tests/message-protocol.test.ts +0 -212
- package/tests/metadata-codec.test.ts +0 -165
- package/tests/metadata-validator.test.ts +0 -261
- package/tests/metering-storage.test.ts +0 -244
- package/tests/payment-codec.test.ts +0 -95
- package/tests/payment-mux.test.ts +0 -191
- package/tests/peer-selector.test.ts +0 -184
- package/tests/provider-detection.test.ts +0 -107
- package/tests/proxy-mux-security.test.ts +0 -38
- package/tests/receipt.test.ts +0 -215
- package/tests/reputation-integration.test.ts +0 -195
- package/tests/request-codec.test.ts +0 -144
- package/tests/token-counter.test.ts +0 -122
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -7
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
encodeSessionLockAuth, decodeSessionLockAuth,
|
|
4
|
-
encodeSessionLockConfirm, decodeSessionLockConfirm,
|
|
5
|
-
encodeSessionLockReject, decodeSessionLockReject,
|
|
6
|
-
encodeSellerReceipt, decodeSellerReceipt,
|
|
7
|
-
encodeBuyerAck, decodeBuyerAck,
|
|
8
|
-
encodeSessionEnd, decodeSessionEnd,
|
|
9
|
-
encodeTopUpRequest, decodeTopUpRequest,
|
|
10
|
-
encodeTopUpAuth, decodeTopUpAuth,
|
|
11
|
-
encodeDisputeNotify, decodeDisputeNotify,
|
|
12
|
-
} from '../src/p2p/payment-codec.js';
|
|
13
|
-
|
|
14
|
-
describe('payment codec round-trips', () => {
|
|
15
|
-
it('SessionLockAuth', () => {
|
|
16
|
-
const payload = {
|
|
17
|
-
sessionId: 'a'.repeat(64),
|
|
18
|
-
lockedAmount: '1000000',
|
|
19
|
-
buyerSig: 'b'.repeat(128),
|
|
20
|
-
};
|
|
21
|
-
const encoded = encodeSessionLockAuth(payload);
|
|
22
|
-
const decoded = decodeSessionLockAuth(encoded);
|
|
23
|
-
expect(decoded).toEqual(payload);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('SessionLockConfirm', () => {
|
|
27
|
-
const payload = { sessionId: 'a'.repeat(64), txSignature: 'tx123' };
|
|
28
|
-
expect(decodeSessionLockConfirm(encodeSessionLockConfirm(payload))).toEqual(payload);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('SessionLockReject', () => {
|
|
32
|
-
const payload = { sessionId: 'a'.repeat(64), reason: 'Insufficient funds' };
|
|
33
|
-
expect(decodeSessionLockReject(encodeSessionLockReject(payload))).toEqual(payload);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('SellerReceipt', () => {
|
|
37
|
-
const payload = {
|
|
38
|
-
sessionId: 'a'.repeat(64),
|
|
39
|
-
runningTotal: '500000',
|
|
40
|
-
requestCount: 5,
|
|
41
|
-
responseHash: 'c'.repeat(64),
|
|
42
|
-
sellerSig: 'd'.repeat(128),
|
|
43
|
-
};
|
|
44
|
-
expect(decodeSellerReceipt(encodeSellerReceipt(payload))).toEqual(payload);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('BuyerAck', () => {
|
|
48
|
-
const payload = {
|
|
49
|
-
sessionId: 'a'.repeat(64),
|
|
50
|
-
runningTotal: '500000',
|
|
51
|
-
requestCount: 5,
|
|
52
|
-
buyerSig: 'e'.repeat(128),
|
|
53
|
-
};
|
|
54
|
-
expect(decodeBuyerAck(encodeBuyerAck(payload))).toEqual(payload);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('SessionEnd', () => {
|
|
58
|
-
const payload = {
|
|
59
|
-
sessionId: 'a'.repeat(64),
|
|
60
|
-
runningTotal: '500000',
|
|
61
|
-
requestCount: 5,
|
|
62
|
-
score: 85,
|
|
63
|
-
buyerSig: 'f'.repeat(128),
|
|
64
|
-
};
|
|
65
|
-
expect(decodeSessionEnd(encodeSessionEnd(payload))).toEqual(payload);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('TopUpRequest', () => {
|
|
69
|
-
const payload = {
|
|
70
|
-
sessionId: 'a'.repeat(64),
|
|
71
|
-
additionalAmount: '500000',
|
|
72
|
-
currentRunningTotal: '400000',
|
|
73
|
-
currentLockedAmount: '500000',
|
|
74
|
-
};
|
|
75
|
-
expect(decodeTopUpRequest(encodeTopUpRequest(payload))).toEqual(payload);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('TopUpAuth', () => {
|
|
79
|
-
const payload = {
|
|
80
|
-
sessionId: 'a'.repeat(64),
|
|
81
|
-
additionalAmount: '500000',
|
|
82
|
-
buyerSig: 'g'.repeat(128),
|
|
83
|
-
};
|
|
84
|
-
expect(decodeTopUpAuth(encodeTopUpAuth(payload))).toEqual(payload);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('DisputeNotify', () => {
|
|
88
|
-
const payload = {
|
|
89
|
-
sessionId: 'a'.repeat(64),
|
|
90
|
-
reason: 'Unacknowledged service',
|
|
91
|
-
txSignature: 'tx456',
|
|
92
|
-
};
|
|
93
|
-
expect(decodeDisputeNotify(encodeDisputeNotify(payload))).toEqual(payload);
|
|
94
|
-
});
|
|
95
|
-
});
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { PaymentMux } from '../src/p2p/payment-mux.js';
|
|
3
|
-
import { MessageType, type FramedMessage } from '../src/types/protocol.js';
|
|
4
|
-
import * as codec from '../src/p2p/payment-codec.js';
|
|
5
|
-
import type { PeerConnection } from '../src/p2p/connection-manager.js';
|
|
6
|
-
|
|
7
|
-
function mockConnection(): PeerConnection {
|
|
8
|
-
return { send: vi.fn() } as unknown as PeerConnection;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
describe('PaymentMux', () => {
|
|
12
|
-
describe('isPaymentMessage correctly identifies range', () => {
|
|
13
|
-
it('returns true for 0x50-0x58', () => {
|
|
14
|
-
for (let type = 0x50; type <= 0x58; type++) {
|
|
15
|
-
expect(PaymentMux.isPaymentMessage(type)).toBe(true);
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('returns true for 0x59-0x5F (rest of payment range)', () => {
|
|
20
|
-
for (let type = 0x59; type <= 0x5f; type++) {
|
|
21
|
-
expect(PaymentMux.isPaymentMessage(type)).toBe(true);
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('returns false for non-payment types', () => {
|
|
26
|
-
expect(PaymentMux.isPaymentMessage(0x01)).toBe(false);
|
|
27
|
-
expect(PaymentMux.isPaymentMessage(0x20)).toBe(false);
|
|
28
|
-
expect(PaymentMux.isPaymentMessage(0x4f)).toBe(false);
|
|
29
|
-
expect(PaymentMux.isPaymentMessage(0x60)).toBe(false);
|
|
30
|
-
expect(PaymentMux.isPaymentMessage(0xff)).toBe(false);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe('handleFrame returns false for non-payment messages', () => {
|
|
35
|
-
it('returns false for HttpRequest', async () => {
|
|
36
|
-
const mux = new PaymentMux(mockConnection());
|
|
37
|
-
const frame: FramedMessage = {
|
|
38
|
-
type: MessageType.HttpRequest,
|
|
39
|
-
messageId: 1,
|
|
40
|
-
payload: new Uint8Array(0),
|
|
41
|
-
};
|
|
42
|
-
expect(await mux.handleFrame(frame)).toBe(false);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('returns false for Ping', async () => {
|
|
46
|
-
const mux = new PaymentMux(mockConnection());
|
|
47
|
-
const frame: FramedMessage = {
|
|
48
|
-
type: MessageType.Ping,
|
|
49
|
-
messageId: 1,
|
|
50
|
-
payload: new Uint8Array(0),
|
|
51
|
-
};
|
|
52
|
-
expect(await mux.handleFrame(frame)).toBe(false);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe('handleFrame dispatches to correct handler', () => {
|
|
57
|
-
it('dispatches SessionLockAuth', async () => {
|
|
58
|
-
const conn = mockConnection();
|
|
59
|
-
const mux = new PaymentMux(conn);
|
|
60
|
-
const handler = vi.fn();
|
|
61
|
-
mux.onSessionLockAuth(handler);
|
|
62
|
-
|
|
63
|
-
const payload = {
|
|
64
|
-
sessionId: 'a'.repeat(64),
|
|
65
|
-
lockedAmount: '1000000',
|
|
66
|
-
buyerSig: 'b'.repeat(128),
|
|
67
|
-
};
|
|
68
|
-
const frame: FramedMessage = {
|
|
69
|
-
type: MessageType.SessionLockAuth,
|
|
70
|
-
messageId: 1,
|
|
71
|
-
payload: codec.encodeSessionLockAuth(payload),
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const result = await mux.handleFrame(frame);
|
|
75
|
-
expect(result).toBe(true);
|
|
76
|
-
expect(handler).toHaveBeenCalledWith(payload);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('dispatches SellerReceipt', async () => {
|
|
80
|
-
const conn = mockConnection();
|
|
81
|
-
const mux = new PaymentMux(conn);
|
|
82
|
-
const handler = vi.fn();
|
|
83
|
-
mux.onSellerReceipt(handler);
|
|
84
|
-
|
|
85
|
-
const payload = {
|
|
86
|
-
sessionId: 'a'.repeat(64),
|
|
87
|
-
runningTotal: '500000',
|
|
88
|
-
requestCount: 5,
|
|
89
|
-
responseHash: 'c'.repeat(64),
|
|
90
|
-
sellerSig: 'd'.repeat(128),
|
|
91
|
-
};
|
|
92
|
-
const frame: FramedMessage = {
|
|
93
|
-
type: MessageType.SellerReceipt,
|
|
94
|
-
messageId: 2,
|
|
95
|
-
payload: codec.encodeSellerReceipt(payload),
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const result = await mux.handleFrame(frame);
|
|
99
|
-
expect(result).toBe(true);
|
|
100
|
-
expect(handler).toHaveBeenCalledWith(payload);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('dispatches BuyerAck', async () => {
|
|
104
|
-
const conn = mockConnection();
|
|
105
|
-
const mux = new PaymentMux(conn);
|
|
106
|
-
const handler = vi.fn();
|
|
107
|
-
mux.onBuyerAck(handler);
|
|
108
|
-
|
|
109
|
-
const payload = {
|
|
110
|
-
sessionId: 'a'.repeat(64),
|
|
111
|
-
runningTotal: '500000',
|
|
112
|
-
requestCount: 5,
|
|
113
|
-
buyerSig: 'e'.repeat(128),
|
|
114
|
-
};
|
|
115
|
-
const frame: FramedMessage = {
|
|
116
|
-
type: MessageType.BuyerAck,
|
|
117
|
-
messageId: 3,
|
|
118
|
-
payload: codec.encodeBuyerAck(payload),
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const result = await mux.handleFrame(frame);
|
|
122
|
-
expect(result).toBe(true);
|
|
123
|
-
expect(handler).toHaveBeenCalledWith(payload);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('dispatches SessionEnd', async () => {
|
|
127
|
-
const conn = mockConnection();
|
|
128
|
-
const mux = new PaymentMux(conn);
|
|
129
|
-
const handler = vi.fn();
|
|
130
|
-
mux.onSessionEnd(handler);
|
|
131
|
-
|
|
132
|
-
const payload = {
|
|
133
|
-
sessionId: 'a'.repeat(64),
|
|
134
|
-
runningTotal: '500000',
|
|
135
|
-
requestCount: 5,
|
|
136
|
-
score: 85,
|
|
137
|
-
buyerSig: 'f'.repeat(128),
|
|
138
|
-
};
|
|
139
|
-
const frame: FramedMessage = {
|
|
140
|
-
type: MessageType.SessionEnd,
|
|
141
|
-
messageId: 4,
|
|
142
|
-
payload: codec.encodeSessionEnd(payload),
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
const result = await mux.handleFrame(frame);
|
|
146
|
-
expect(result).toBe(true);
|
|
147
|
-
expect(handler).toHaveBeenCalledWith(payload);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('dispatches DisputeNotify', async () => {
|
|
151
|
-
const conn = mockConnection();
|
|
152
|
-
const mux = new PaymentMux(conn);
|
|
153
|
-
const handler = vi.fn();
|
|
154
|
-
mux.onDisputeNotify(handler);
|
|
155
|
-
|
|
156
|
-
const payload = {
|
|
157
|
-
sessionId: 'a'.repeat(64),
|
|
158
|
-
reason: 'Unacknowledged service',
|
|
159
|
-
txSignature: 'tx456',
|
|
160
|
-
};
|
|
161
|
-
const frame: FramedMessage = {
|
|
162
|
-
type: MessageType.DisputeNotify,
|
|
163
|
-
messageId: 5,
|
|
164
|
-
payload: codec.encodeDisputeNotify(payload),
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
const result = await mux.handleFrame(frame);
|
|
168
|
-
expect(result).toBe(true);
|
|
169
|
-
expect(handler).toHaveBeenCalledWith(payload);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it('returns true even with no handler registered', async () => {
|
|
173
|
-
const conn = mockConnection();
|
|
174
|
-
const mux = new PaymentMux(conn);
|
|
175
|
-
|
|
176
|
-
const payload = {
|
|
177
|
-
sessionId: 'a'.repeat(64),
|
|
178
|
-
lockedAmount: '1000000',
|
|
179
|
-
buyerSig: 'b'.repeat(128),
|
|
180
|
-
};
|
|
181
|
-
const frame: FramedMessage = {
|
|
182
|
-
type: MessageType.SessionLockAuth,
|
|
183
|
-
messageId: 1,
|
|
184
|
-
payload: codec.encodeSessionLockAuth(payload),
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const result = await mux.handleFrame(frame);
|
|
188
|
-
expect(result).toBe(true);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
});
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
scorePeer,
|
|
4
|
-
rankPeers,
|
|
5
|
-
selectBestPeer,
|
|
6
|
-
selectDiversePeers,
|
|
7
|
-
DEFAULT_SCORING_WEIGHTS,
|
|
8
|
-
type PeerCandidate,
|
|
9
|
-
} from '../src/discovery/peer-selector.js';
|
|
10
|
-
|
|
11
|
-
function makeCandidate(overrides?: Partial<PeerCandidate>): PeerCandidate {
|
|
12
|
-
return {
|
|
13
|
-
peerId: 'peer-1',
|
|
14
|
-
region: 'us-east-1',
|
|
15
|
-
inputUsdPerMillion: 0.01,
|
|
16
|
-
maxConcurrency: 10,
|
|
17
|
-
currentLoad: 0,
|
|
18
|
-
latencyMs: 100,
|
|
19
|
-
reputation: 0.9,
|
|
20
|
-
...overrides,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
describe('scorePeer', () => {
|
|
25
|
-
it('should return a score between 0 and 1', () => {
|
|
26
|
-
const score = scorePeer(makeCandidate(), 0.01);
|
|
27
|
-
expect(score).toBeGreaterThanOrEqual(0);
|
|
28
|
-
expect(score).toBeLessThanOrEqual(1);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('should give perfect score to ideal candidate', () => {
|
|
32
|
-
const candidate = makeCandidate({
|
|
33
|
-
inputUsdPerMillion: 0.01,
|
|
34
|
-
maxConcurrency: 10,
|
|
35
|
-
currentLoad: 0,
|
|
36
|
-
latencyMs: 0,
|
|
37
|
-
reputation: 1,
|
|
38
|
-
});
|
|
39
|
-
const score = scorePeer(candidate, 0.01);
|
|
40
|
-
expect(score).toBeCloseTo(1.0, 2);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should give lower score to more expensive peers', () => {
|
|
44
|
-
const cheap = makeCandidate({ inputUsdPerMillion: 0.01 });
|
|
45
|
-
const expensive = makeCandidate({ inputUsdPerMillion: 0.10 });
|
|
46
|
-
const cheapScore = scorePeer(cheap, 0.01);
|
|
47
|
-
const expensiveScore = scorePeer(expensive, 0.01);
|
|
48
|
-
expect(cheapScore).toBeGreaterThan(expensiveScore);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should give lower score to heavily loaded peers', () => {
|
|
52
|
-
const idle = makeCandidate({ currentLoad: 0, maxConcurrency: 10 });
|
|
53
|
-
const loaded = makeCandidate({ currentLoad: 9, maxConcurrency: 10 });
|
|
54
|
-
expect(scorePeer(idle, 0.01)).toBeGreaterThan(scorePeer(loaded, 0.01));
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should give lower score to high-latency peers', () => {
|
|
58
|
-
const fast = makeCandidate({ latencyMs: 50 });
|
|
59
|
-
const slow = makeCandidate({ latencyMs: 10000 });
|
|
60
|
-
expect(scorePeer(fast, 0.01)).toBeGreaterThan(scorePeer(slow, 0.01));
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should handle zero price (free) as perfect price score', () => {
|
|
64
|
-
const free = makeCandidate({ inputUsdPerMillion: 0 });
|
|
65
|
-
const score = scorePeer(free, 0.01);
|
|
66
|
-
// price score = 1.0 for free peers
|
|
67
|
-
expect(score).toBeGreaterThanOrEqual(0);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should handle maxConcurrency = 0 as zero capacity', () => {
|
|
71
|
-
const candidate = makeCandidate({ maxConcurrency: 0, currentLoad: 0 });
|
|
72
|
-
// capacity score = 0 when maxConcurrency is 0
|
|
73
|
-
const score = scorePeer(candidate, 0.01);
|
|
74
|
-
expect(score).toBeLessThan(1);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('should clamp latency score at 0 for >= 15000ms', () => {
|
|
78
|
-
const verySlowA = makeCandidate({ latencyMs: 15000 });
|
|
79
|
-
const verySlowB = makeCandidate({ latencyMs: 20000 });
|
|
80
|
-
expect(scorePeer(verySlowA, 0.01)).toEqual(scorePeer(verySlowB, 0.01));
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should accept custom weights', () => {
|
|
84
|
-
// Use a candidate where price score and capacity score differ
|
|
85
|
-
const candidate = makeCandidate({
|
|
86
|
-
inputUsdPerMillion: 0.05, // price score = 0.01/0.05 = 0.2
|
|
87
|
-
maxConcurrency: 10,
|
|
88
|
-
currentLoad: 5, // capacity score = 5/10 = 0.5
|
|
89
|
-
latencyMs: 0,
|
|
90
|
-
reputation: 0,
|
|
91
|
-
});
|
|
92
|
-
const priceOnly = { price: 1, capacity: 0, latency: 0, reputation: 0 };
|
|
93
|
-
const capOnly = { price: 0, capacity: 1, latency: 0, reputation: 0 };
|
|
94
|
-
const s1 = scorePeer(candidate, 0.01, priceOnly);
|
|
95
|
-
const s2 = scorePeer(candidate, 0.01, capOnly);
|
|
96
|
-
expect(s1).not.toBe(s2);
|
|
97
|
-
expect(s1).toBeCloseTo(0.2, 2);
|
|
98
|
-
expect(s2).toBeCloseTo(0.5, 2);
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
describe('rankPeers', () => {
|
|
103
|
-
it('should return empty array for empty input', () => {
|
|
104
|
-
expect(rankPeers([])).toEqual([]);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('should sort peers by score descending', () => {
|
|
108
|
-
const candidates = [
|
|
109
|
-
makeCandidate({ peerId: 'expensive', inputUsdPerMillion: 1.0, latencyMs: 5000 }),
|
|
110
|
-
makeCandidate({ peerId: 'cheap', inputUsdPerMillion: 0.001, latencyMs: 50 }),
|
|
111
|
-
];
|
|
112
|
-
const ranked = rankPeers(candidates);
|
|
113
|
-
expect(ranked[0]!.candidate.peerId).toBe('cheap');
|
|
114
|
-
expect(ranked[0]!.score).toBeGreaterThan(ranked[1]!.score);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('should derive cheapest price automatically', () => {
|
|
118
|
-
const candidates = [
|
|
119
|
-
makeCandidate({ peerId: 'a', inputUsdPerMillion: 0.1 }),
|
|
120
|
-
makeCandidate({ peerId: 'b', inputUsdPerMillion: 0.01 }),
|
|
121
|
-
];
|
|
122
|
-
const ranked = rankPeers(candidates);
|
|
123
|
-
// 'b' is cheapest so it should have perfect price score
|
|
124
|
-
expect(ranked[0]!.candidate.peerId).toBe('b');
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
describe('selectBestPeer', () => {
|
|
129
|
-
it('should return null for empty candidates', () => {
|
|
130
|
-
expect(selectBestPeer([])).toBeNull();
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('should return the highest-scoring peer', () => {
|
|
134
|
-
const candidates = [
|
|
135
|
-
makeCandidate({ peerId: 'slow', latencyMs: 10000, inputUsdPerMillion: 1 }),
|
|
136
|
-
makeCandidate({ peerId: 'fast', latencyMs: 10, inputUsdPerMillion: 0.001 }),
|
|
137
|
-
];
|
|
138
|
-
const best = selectBestPeer(candidates);
|
|
139
|
-
expect(best).not.toBeNull();
|
|
140
|
-
expect(best!.candidate.peerId).toBe('fast');
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe('selectDiversePeers', () => {
|
|
145
|
-
it('should return all peers when count >= candidates', () => {
|
|
146
|
-
const candidates = [makeCandidate({ peerId: 'a' }), makeCandidate({ peerId: 'b' })];
|
|
147
|
-
const result = selectDiversePeers(candidates, 5);
|
|
148
|
-
expect(result).toHaveLength(2);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('should prefer peers from different regions', () => {
|
|
152
|
-
const candidates = [
|
|
153
|
-
makeCandidate({ peerId: 'us1', region: 'us-east-1', inputUsdPerMillion: 0.01 }),
|
|
154
|
-
makeCandidate({ peerId: 'us2', region: 'us-east-1', inputUsdPerMillion: 0.02 }),
|
|
155
|
-
makeCandidate({ peerId: 'eu1', region: 'eu-west-1', inputUsdPerMillion: 0.03 }),
|
|
156
|
-
];
|
|
157
|
-
const result = selectDiversePeers(candidates, 2);
|
|
158
|
-
expect(result).toHaveLength(2);
|
|
159
|
-
const regions = result.map((r) => r.candidate.region);
|
|
160
|
-
expect(regions).toContain('us-east-1');
|
|
161
|
-
expect(regions).toContain('eu-west-1');
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('should fill remaining slots by score after region diversity', () => {
|
|
165
|
-
const candidates = [
|
|
166
|
-
makeCandidate({ peerId: 'a', region: 'us', inputUsdPerMillion: 0.001 }),
|
|
167
|
-
makeCandidate({ peerId: 'b', region: 'us', inputUsdPerMillion: 0.01 }),
|
|
168
|
-
makeCandidate({ peerId: 'c', region: 'eu', inputUsdPerMillion: 0.1 }),
|
|
169
|
-
];
|
|
170
|
-
const result = selectDiversePeers(candidates, 3);
|
|
171
|
-
expect(result).toHaveLength(3);
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
describe('DEFAULT_SCORING_WEIGHTS', () => {
|
|
176
|
-
it('should sum to approximately 1.0', () => {
|
|
177
|
-
const sum =
|
|
178
|
-
DEFAULT_SCORING_WEIGHTS.price +
|
|
179
|
-
DEFAULT_SCORING_WEIGHTS.capacity +
|
|
180
|
-
DEFAULT_SCORING_WEIGHTS.latency +
|
|
181
|
-
DEFAULT_SCORING_WEIGHTS.reputation;
|
|
182
|
-
expect(sum).toBeCloseTo(1.0, 5);
|
|
183
|
-
});
|
|
184
|
-
});
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
detectProviderFromHeaders,
|
|
4
|
-
detectProviderFromPath,
|
|
5
|
-
resolveProvider,
|
|
6
|
-
ANTSEED_PROVIDER_HEADER,
|
|
7
|
-
} from '../src/proxy/provider-detection.js';
|
|
8
|
-
|
|
9
|
-
describe('detectProviderFromHeaders', () => {
|
|
10
|
-
it('should detect anthropic from header', () => {
|
|
11
|
-
expect(detectProviderFromHeaders({ [ANTSEED_PROVIDER_HEADER]: 'anthropic' })).toBe('anthropic');
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('should detect openai from header', () => {
|
|
15
|
-
expect(detectProviderFromHeaders({ [ANTSEED_PROVIDER_HEADER]: 'openai' })).toBe('openai');
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('should detect google from header', () => {
|
|
19
|
-
expect(detectProviderFromHeaders({ [ANTSEED_PROVIDER_HEADER]: 'google' })).toBe('google');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should detect moonshot from header', () => {
|
|
23
|
-
expect(detectProviderFromHeaders({ [ANTSEED_PROVIDER_HEADER]: 'moonshot' })).toBe('moonshot');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should be case-insensitive for header name', () => {
|
|
27
|
-
expect(detectProviderFromHeaders({ 'X-ANTSEED-PROVIDER': 'anthropic' })).toBe('anthropic');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('should trim and lowercase the header value', () => {
|
|
31
|
-
expect(detectProviderFromHeaders({ [ANTSEED_PROVIDER_HEADER]: ' OPENAI ' })).toBe('openai');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('should return null for unknown provider', () => {
|
|
35
|
-
expect(detectProviderFromHeaders({ [ANTSEED_PROVIDER_HEADER]: 'unknown' })).toBeNull();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should return null when header is missing', () => {
|
|
39
|
-
expect(detectProviderFromHeaders({ 'content-type': 'application/json' })).toBeNull();
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe('detectProviderFromPath', () => {
|
|
44
|
-
it('should detect anthropic from /v1/messages', () => {
|
|
45
|
-
expect(detectProviderFromPath('/v1/messages')).toBe('anthropic');
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should detect anthropic from /v1/complete', () => {
|
|
49
|
-
expect(detectProviderFromPath('/v1/complete')).toBe('anthropic');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should detect openai from /v1/chat/completions', () => {
|
|
53
|
-
expect(detectProviderFromPath('/v1/chat/completions')).toBe('openai');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('should detect openai from /v1/completions', () => {
|
|
57
|
-
expect(detectProviderFromPath('/v1/completions')).toBe('openai');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('should detect openai from /v1/embeddings', () => {
|
|
61
|
-
expect(detectProviderFromPath('/v1/embeddings')).toBe('openai');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('should detect google from /v1beta/ paths', () => {
|
|
65
|
-
expect(detectProviderFromPath('/v1beta/models/gemini-pro:generateContent')).toBe('google');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('should detect google from /v1/models/gemini paths', () => {
|
|
69
|
-
expect(detectProviderFromPath('/v1/models/gemini-pro:generateContent')).toBe('google');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should detect moonshot from paths containing moonshot', () => {
|
|
73
|
-
expect(detectProviderFromPath('/v1/moonshot/chat/completions')).toBe('moonshot');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('should prioritize moonshot over openai for ambiguous paths', () => {
|
|
77
|
-
// moonshot check happens before openai
|
|
78
|
-
expect(detectProviderFromPath('/v1/chat/moonshot/completions')).toBe('moonshot');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('should return null for unrecognized paths', () => {
|
|
82
|
-
expect(detectProviderFromPath('/api/v2/infer')).toBeNull();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('should be case-insensitive', () => {
|
|
86
|
-
expect(detectProviderFromPath('/V1/Messages')).toBe('anthropic');
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe('resolveProvider', () => {
|
|
91
|
-
it('should prefer header over path', () => {
|
|
92
|
-
const result = resolveProvider('/v1/chat/completions', {
|
|
93
|
-
[ANTSEED_PROVIDER_HEADER]: 'anthropic',
|
|
94
|
-
}, 'openai');
|
|
95
|
-
expect(result).toBe('anthropic');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('should fall back to path when header is absent', () => {
|
|
99
|
-
const result = resolveProvider('/v1/messages', {}, 'openai');
|
|
100
|
-
expect(result).toBe('anthropic');
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('should fall back to default when neither header nor path matches', () => {
|
|
104
|
-
const result = resolveProvider('/unknown', {}, 'openai');
|
|
105
|
-
expect(result).toBe('openai');
|
|
106
|
-
});
|
|
107
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { ProxyMux } from '../src/proxy/proxy-mux.js';
|
|
3
|
-
import { MessageType } from '../src/types/protocol.js';
|
|
4
|
-
import type { PeerConnection } from '../src/p2p/connection-manager.js';
|
|
5
|
-
|
|
6
|
-
function createMux(): ProxyMux {
|
|
7
|
-
const conn = {
|
|
8
|
-
send: () => {},
|
|
9
|
-
} as unknown as PeerConnection;
|
|
10
|
-
|
|
11
|
-
return new ProxyMux(conn);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
describe('ProxyMux security handling', () => {
|
|
15
|
-
it('rejects malformed payloads instead of silently proceeding', async () => {
|
|
16
|
-
const mux = createMux();
|
|
17
|
-
|
|
18
|
-
await expect(
|
|
19
|
-
mux.handleFrame({
|
|
20
|
-
type: MessageType.HttpResponse,
|
|
21
|
-
messageId: 1,
|
|
22
|
-
payload: new Uint8Array([0x01]),
|
|
23
|
-
}),
|
|
24
|
-
).rejects.toThrow('Failed to handle proxy frame type');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('ignores unknown frame types', async () => {
|
|
28
|
-
const mux = createMux();
|
|
29
|
-
|
|
30
|
-
await expect(
|
|
31
|
-
mux.handleFrame({
|
|
32
|
-
type: 0x99 as MessageType,
|
|
33
|
-
messageId: 2,
|
|
34
|
-
payload: new Uint8Array(),
|
|
35
|
-
}),
|
|
36
|
-
).resolves.toBeUndefined();
|
|
37
|
-
});
|
|
38
|
-
});
|