@antseed/node 0.1.0 → 0.1.1
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/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 +123 -15
- 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
package/tests/receipt.test.ts
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
ReceiptGenerator,
|
|
4
|
-
buildSignaturePayload,
|
|
5
|
-
calculateCost,
|
|
6
|
-
type Signer,
|
|
7
|
-
} from '../src/metering/receipt-generator.js';
|
|
8
|
-
import { ReceiptVerifier, type SignatureVerifier } from '../src/metering/receipt-verifier.js';
|
|
9
|
-
import type { TokenCount, UsageReceipt } from '../src/types/metering.js';
|
|
10
|
-
|
|
11
|
-
function makeTokenCount(total: number): TokenCount {
|
|
12
|
-
return {
|
|
13
|
-
inputTokens: Math.floor(total * 0.6),
|
|
14
|
-
outputTokens: Math.ceil(total * 0.4),
|
|
15
|
-
totalTokens: total,
|
|
16
|
-
method: 'content-length',
|
|
17
|
-
confidence: 'high',
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function makeSigner(peerId: string): Signer {
|
|
22
|
-
return {
|
|
23
|
-
peerId,
|
|
24
|
-
sign(message: string): string {
|
|
25
|
-
// Deterministic fake signature for testing
|
|
26
|
-
return 'f'.repeat(128);
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function makeVerifier(alwaysValid: boolean): SignatureVerifier {
|
|
32
|
-
return {
|
|
33
|
-
verify(_message: string, _signature: string, _publicKeyHex: string): boolean {
|
|
34
|
-
return alwaysValid;
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
describe('calculateCost', () => {
|
|
40
|
-
it('should calculate cost for 1000 tokens at 10 cents/1K', () => {
|
|
41
|
-
expect(calculateCost(1000, 10)).toBe(10);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should round to nearest cent', () => {
|
|
45
|
-
// 1500 tokens at 10 cents/1K = 15 cents
|
|
46
|
-
expect(calculateCost(1500, 10)).toBe(15);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should return minimum of 1 cent for non-zero cost', () => {
|
|
50
|
-
// 1 token at 0.001 cents/1K = 0.000001 cents -> rounds to 0, but min is 1
|
|
51
|
-
expect(calculateCost(1, 1)).toBe(1);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should return 0 for zero tokens', () => {
|
|
55
|
-
expect(calculateCost(0, 10)).toBe(0);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('should return 0 for zero price', () => {
|
|
59
|
-
expect(calculateCost(1000, 0)).toBe(0);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe('buildSignaturePayload', () => {
|
|
64
|
-
it('should produce a pipe-delimited string', () => {
|
|
65
|
-
const payload = buildSignaturePayload({
|
|
66
|
-
receiptId: 'r1',
|
|
67
|
-
sessionId: 's1',
|
|
68
|
-
eventId: 'e1',
|
|
69
|
-
timestamp: 1000,
|
|
70
|
-
provider: 'openai',
|
|
71
|
-
sellerPeerId: 'seller',
|
|
72
|
-
buyerPeerId: 'buyer',
|
|
73
|
-
tokens: makeTokenCount(500),
|
|
74
|
-
unitPriceCentsPerThousandTokens: 10,
|
|
75
|
-
costCents: 5,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
expect(payload).toBe('r1|s1|e1|1000|openai|seller|buyer|500|5');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('should be deterministic', () => {
|
|
82
|
-
const data = {
|
|
83
|
-
receiptId: 'r1',
|
|
84
|
-
sessionId: 's1',
|
|
85
|
-
eventId: 'e1',
|
|
86
|
-
timestamp: 1000,
|
|
87
|
-
provider: 'openai' as const,
|
|
88
|
-
sellerPeerId: 'seller',
|
|
89
|
-
buyerPeerId: 'buyer',
|
|
90
|
-
tokens: makeTokenCount(500),
|
|
91
|
-
unitPriceCentsPerThousandTokens: 10,
|
|
92
|
-
costCents: 5,
|
|
93
|
-
};
|
|
94
|
-
expect(buildSignaturePayload(data)).toBe(buildSignaturePayload(data));
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
describe('ReceiptGenerator', () => {
|
|
99
|
-
it('should generate a complete receipt', () => {
|
|
100
|
-
const signer = makeSigner('seller-peer-id');
|
|
101
|
-
const generator = new ReceiptGenerator(signer);
|
|
102
|
-
|
|
103
|
-
const receipt = generator.generate(
|
|
104
|
-
'session-1',
|
|
105
|
-
'event-1',
|
|
106
|
-
'anthropic',
|
|
107
|
-
'buyer-peer-id',
|
|
108
|
-
makeTokenCount(2000),
|
|
109
|
-
10
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
expect(receipt.receiptId).toBeTruthy();
|
|
113
|
-
expect(receipt.sessionId).toBe('session-1');
|
|
114
|
-
expect(receipt.eventId).toBe('event-1');
|
|
115
|
-
expect(receipt.provider).toBe('anthropic');
|
|
116
|
-
expect(receipt.sellerPeerId).toBe('seller-peer-id');
|
|
117
|
-
expect(receipt.buyerPeerId).toBe('buyer-peer-id');
|
|
118
|
-
expect(receipt.tokens.totalTokens).toBe(2000);
|
|
119
|
-
expect(receipt.unitPriceCentsPerThousandTokens).toBe(10);
|
|
120
|
-
expect(receipt.costCents).toBe(20); // 2000/1000 * 10
|
|
121
|
-
expect(receipt.signature).toBeTruthy();
|
|
122
|
-
expect(receipt.timestamp).toBeGreaterThan(0);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('should produce unique receipt IDs', () => {
|
|
126
|
-
const signer = makeSigner('seller');
|
|
127
|
-
const generator = new ReceiptGenerator(signer);
|
|
128
|
-
const tokens = makeTokenCount(100);
|
|
129
|
-
|
|
130
|
-
const r1 = generator.generate('s', 'e1', 'openai', 'buyer', tokens, 1);
|
|
131
|
-
const r2 = generator.generate('s', 'e2', 'openai', 'buyer', tokens, 1);
|
|
132
|
-
expect(r1.receiptId).not.toBe(r2.receiptId);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
describe('ReceiptVerifier', () => {
|
|
137
|
-
it('should pass verification for valid signature within threshold', () => {
|
|
138
|
-
const verifier = new ReceiptVerifier(makeVerifier(true));
|
|
139
|
-
|
|
140
|
-
const receipt: UsageReceipt = {
|
|
141
|
-
receiptId: 'r1',
|
|
142
|
-
sessionId: 's1',
|
|
143
|
-
eventId: 'e1',
|
|
144
|
-
timestamp: Date.now(),
|
|
145
|
-
provider: 'openai',
|
|
146
|
-
sellerPeerId: 'seller',
|
|
147
|
-
buyerPeerId: 'buyer',
|
|
148
|
-
tokens: makeTokenCount(1000),
|
|
149
|
-
unitPriceCentsPerThousandTokens: 10,
|
|
150
|
-
costCents: 10,
|
|
151
|
-
signature: 'f'.repeat(128),
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const buyerEstimate = makeTokenCount(1050); // 5% difference
|
|
155
|
-
const result = verifier.verify(receipt, buyerEstimate);
|
|
156
|
-
|
|
157
|
-
expect(result.signatureValid).toBe(true);
|
|
158
|
-
expect(result.disputed).toBe(false);
|
|
159
|
-
expect(result.percentageDifference).toBeCloseTo(4.76, 1); // |1000-1050|/1050*100
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('should flag as disputed when signature is invalid', () => {
|
|
163
|
-
const verifier = new ReceiptVerifier(makeVerifier(false));
|
|
164
|
-
const receipt: UsageReceipt = {
|
|
165
|
-
receiptId: 'r1',
|
|
166
|
-
sessionId: 's1',
|
|
167
|
-
eventId: 'e1',
|
|
168
|
-
timestamp: Date.now(),
|
|
169
|
-
provider: 'openai',
|
|
170
|
-
sellerPeerId: 'seller',
|
|
171
|
-
buyerPeerId: 'buyer',
|
|
172
|
-
tokens: makeTokenCount(1000),
|
|
173
|
-
unitPriceCentsPerThousandTokens: 10,
|
|
174
|
-
costCents: 10,
|
|
175
|
-
signature: 'bad',
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
const result = verifier.verify(receipt, makeTokenCount(1000));
|
|
179
|
-
expect(result.signatureValid).toBe(false);
|
|
180
|
-
expect(result.disputed).toBe(true);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('should flag as disputed when token difference exceeds threshold', () => {
|
|
184
|
-
const verifier = new ReceiptVerifier(makeVerifier(true), { disputeThresholdPercent: 10 });
|
|
185
|
-
|
|
186
|
-
const receipt: UsageReceipt = {
|
|
187
|
-
receiptId: 'r1',
|
|
188
|
-
sessionId: 's1',
|
|
189
|
-
eventId: 'e1',
|
|
190
|
-
timestamp: Date.now(),
|
|
191
|
-
provider: 'openai',
|
|
192
|
-
sellerPeerId: 'seller',
|
|
193
|
-
buyerPeerId: 'buyer',
|
|
194
|
-
tokens: makeTokenCount(1000),
|
|
195
|
-
unitPriceCentsPerThousandTokens: 10,
|
|
196
|
-
costCents: 10,
|
|
197
|
-
signature: 'f'.repeat(128),
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
const buyerEstimate = makeTokenCount(800); // 20% difference
|
|
201
|
-
const result = verifier.verify(receipt, buyerEstimate);
|
|
202
|
-
|
|
203
|
-
expect(result.signatureValid).toBe(true);
|
|
204
|
-
expect(result.disputed).toBe(true);
|
|
205
|
-
expect(result.percentageDifference).toBeGreaterThan(10);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it('should calculate percentage difference correctly', () => {
|
|
209
|
-
expect(ReceiptVerifier.calculatePercentageDifference(100, 100)).toBe(0);
|
|
210
|
-
expect(ReceiptVerifier.calculatePercentageDifference(100, 200)).toBe(50);
|
|
211
|
-
expect(ReceiptVerifier.calculatePercentageDifference(200, 100)).toBe(50);
|
|
212
|
-
expect(ReceiptVerifier.calculatePercentageDifference(0, 0)).toBe(0);
|
|
213
|
-
expect(ReceiptVerifier.calculatePercentageDifference(0, 100)).toBe(100);
|
|
214
|
-
});
|
|
215
|
-
});
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { encodeMetadata, decodeMetadata } from '../src/discovery/metadata-codec.js';
|
|
3
|
-
import type { PeerMetadata } from '../src/discovery/peer-metadata.js';
|
|
4
|
-
import type { PeerInfo } from '../src/types/peer.js';
|
|
5
|
-
|
|
6
|
-
function makeMetadata(overrides?: Partial<PeerMetadata>): PeerMetadata {
|
|
7
|
-
return {
|
|
8
|
-
peerId: 'a'.repeat(64) as any,
|
|
9
|
-
version: 2,
|
|
10
|
-
providers: [
|
|
11
|
-
{
|
|
12
|
-
provider: 'anthropic',
|
|
13
|
-
models: ['claude-3-opus'],
|
|
14
|
-
defaultPricing: {
|
|
15
|
-
inputUsdPerMillion: 15,
|
|
16
|
-
outputUsdPerMillion: 75,
|
|
17
|
-
},
|
|
18
|
-
maxConcurrency: 10,
|
|
19
|
-
currentLoad: 3,
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
region: 'us-east-1',
|
|
23
|
-
timestamp: 1700000000000,
|
|
24
|
-
signature: 'b'.repeat(128),
|
|
25
|
-
...overrides,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
describe('Reputation Integration', () => {
|
|
30
|
-
it('should round-trip metadata with EVM address and reputation', () => {
|
|
31
|
-
const original = makeMetadata({
|
|
32
|
-
evmAddress: '0x1234567890abcdef1234567890abcdef12345678',
|
|
33
|
-
onChainReputation: 85,
|
|
34
|
-
onChainSessionCount: 42,
|
|
35
|
-
onChainDisputeCount: 2,
|
|
36
|
-
});
|
|
37
|
-
const encoded = encodeMetadata(original);
|
|
38
|
-
const decoded = decodeMetadata(encoded);
|
|
39
|
-
|
|
40
|
-
expect(decoded.evmAddress).toBe('0x1234567890abcdef1234567890abcdef12345678');
|
|
41
|
-
expect(decoded.onChainReputation).toBe(85);
|
|
42
|
-
expect(decoded.onChainSessionCount).toBe(42);
|
|
43
|
-
expect(decoded.onChainDisputeCount).toBe(2);
|
|
44
|
-
// Verify other fields are still correct
|
|
45
|
-
expect(decoded.peerId).toBe(original.peerId);
|
|
46
|
-
expect(decoded.region).toBe(original.region);
|
|
47
|
-
expect(decoded.timestamp).toBe(original.timestamp);
|
|
48
|
-
expect(decoded.providers).toHaveLength(1);
|
|
49
|
-
expect(decoded.providers[0]!.provider).toBe('anthropic');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should decode metadata without reputation fields (backward compat)', () => {
|
|
53
|
-
// Encode without reputation fields
|
|
54
|
-
const original = makeMetadata();
|
|
55
|
-
const encoded = encodeMetadata(original);
|
|
56
|
-
const decoded = decodeMetadata(encoded);
|
|
57
|
-
|
|
58
|
-
expect(decoded.evmAddress).toBeUndefined();
|
|
59
|
-
expect(decoded.onChainReputation).toBeUndefined();
|
|
60
|
-
expect(decoded.onChainSessionCount).toBeUndefined();
|
|
61
|
-
expect(decoded.onChainDisputeCount).toBeUndefined();
|
|
62
|
-
// Core fields should still work
|
|
63
|
-
expect(decoded.peerId).toBe(original.peerId);
|
|
64
|
-
expect(decoded.region).toBe(original.region);
|
|
65
|
-
expect(decoded.timestamp).toBe(original.timestamp);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('should populate PeerInfo from metadata reputation', () => {
|
|
69
|
-
const metadata: PeerMetadata = makeMetadata({
|
|
70
|
-
evmAddress: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef',
|
|
71
|
-
onChainReputation: 92,
|
|
72
|
-
onChainSessionCount: 100,
|
|
73
|
-
onChainDisputeCount: 1,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// Simulate what _lookupResultToPeerInfo does
|
|
77
|
-
const peerInfo: PeerInfo = {
|
|
78
|
-
peerId: metadata.peerId,
|
|
79
|
-
lastSeen: metadata.timestamp,
|
|
80
|
-
providers: metadata.providers.map((p) => p.provider),
|
|
81
|
-
publicAddress: '1.2.3.4:6882',
|
|
82
|
-
evmAddress: metadata.evmAddress,
|
|
83
|
-
onChainReputation: metadata.onChainReputation,
|
|
84
|
-
onChainSessionCount: metadata.onChainSessionCount,
|
|
85
|
-
onChainDisputeCount: metadata.onChainDisputeCount,
|
|
86
|
-
trustScore: metadata.onChainReputation,
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
expect(peerInfo.evmAddress).toBe('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef');
|
|
90
|
-
expect(peerInfo.onChainReputation).toBe(92);
|
|
91
|
-
expect(peerInfo.onChainSessionCount).toBe(100);
|
|
92
|
-
expect(peerInfo.onChainDisputeCount).toBe(1);
|
|
93
|
-
expect(peerInfo.trustScore).toBe(92);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('should prefer on-chain reputation in effective reputation', () => {
|
|
97
|
-
// Simulates the _effectiveReputation logic from the router
|
|
98
|
-
function effectiveReputation(p: PeerInfo): number {
|
|
99
|
-
if (p.onChainReputation !== undefined) {
|
|
100
|
-
return p.onChainReputation;
|
|
101
|
-
}
|
|
102
|
-
return p.trustScore ?? p.reputationScore ?? 0;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const peer: PeerInfo = {
|
|
106
|
-
peerId: 'a'.repeat(64) as any,
|
|
107
|
-
lastSeen: Date.now(),
|
|
108
|
-
providers: ['anthropic'],
|
|
109
|
-
onChainReputation: 88,
|
|
110
|
-
trustScore: 70,
|
|
111
|
-
reputationScore: 60,
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
expect(effectiveReputation(peer)).toBe(88);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('should fall back when on-chain reputation is not available', () => {
|
|
118
|
-
function effectiveReputation(p: PeerInfo): number {
|
|
119
|
-
if (p.onChainReputation !== undefined) {
|
|
120
|
-
return p.onChainReputation;
|
|
121
|
-
}
|
|
122
|
-
return p.trustScore ?? p.reputationScore ?? 0;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const peerWithTrust: PeerInfo = {
|
|
126
|
-
peerId: 'a'.repeat(64) as any,
|
|
127
|
-
lastSeen: Date.now(),
|
|
128
|
-
providers: ['anthropic'],
|
|
129
|
-
trustScore: 75,
|
|
130
|
-
reputationScore: 60,
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const peerWithRepOnly: PeerInfo = {
|
|
134
|
-
peerId: 'b'.repeat(64) as any,
|
|
135
|
-
lastSeen: Date.now(),
|
|
136
|
-
providers: ['openai'],
|
|
137
|
-
reputationScore: 55,
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const peerWithNothing: PeerInfo = {
|
|
141
|
-
peerId: 'c'.repeat(64) as any,
|
|
142
|
-
lastSeen: Date.now(),
|
|
143
|
-
providers: ['openai'],
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
expect(effectiveReputation(peerWithTrust)).toBe(75);
|
|
147
|
-
expect(effectiveReputation(peerWithRepOnly)).toBe(55);
|
|
148
|
-
expect(effectiveReputation(peerWithNothing)).toBe(0);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('should verify reputation with evmAddress via verifyReputation', async () => {
|
|
152
|
-
const { verifyReputation } = await import('../src/discovery/reputation-verifier.js');
|
|
153
|
-
|
|
154
|
-
const metadata = makeMetadata({
|
|
155
|
-
evmAddress: '0x1111111111111111111111111111111111111111',
|
|
156
|
-
onChainReputation: 80,
|
|
157
|
-
onChainSessionCount: 50,
|
|
158
|
-
onChainDisputeCount: 3,
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// Mock escrow client
|
|
162
|
-
const mockEscrowClient = {
|
|
163
|
-
getReputation: async (_addr: string) => ({
|
|
164
|
-
totalWeightedScore: 4000n,
|
|
165
|
-
totalWeight: 50n,
|
|
166
|
-
sessionCount: 50,
|
|
167
|
-
disputeCount: 3,
|
|
168
|
-
weightedAverage: 80,
|
|
169
|
-
}),
|
|
170
|
-
} as any;
|
|
171
|
-
|
|
172
|
-
const result = await verifyReputation(mockEscrowClient, metadata);
|
|
173
|
-
|
|
174
|
-
expect(result.valid).toBe(true);
|
|
175
|
-
expect(result.actualReputation).toBe(80);
|
|
176
|
-
expect(result.actualSessionCount).toBe(50);
|
|
177
|
-
expect(result.actualDisputeCount).toBe(3);
|
|
178
|
-
expect(result.claimedReputation).toBe(80);
|
|
179
|
-
expect(result.claimedSessionCount).toBe(50);
|
|
180
|
-
expect(result.claimedDisputeCount).toBe(3);
|
|
181
|
-
|
|
182
|
-
// Test with mismatched data
|
|
183
|
-
const mismatchedMetadata = makeMetadata({
|
|
184
|
-
evmAddress: '0x1111111111111111111111111111111111111111',
|
|
185
|
-
onChainReputation: 90, // claimed higher than actual
|
|
186
|
-
onChainSessionCount: 50,
|
|
187
|
-
onChainDisputeCount: 3,
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
const mismatchResult = await verifyReputation(mockEscrowClient, mismatchedMetadata);
|
|
191
|
-
expect(mismatchResult.valid).toBe(false);
|
|
192
|
-
expect(mismatchResult.actualReputation).toBe(80);
|
|
193
|
-
expect(mismatchResult.claimedReputation).toBe(90);
|
|
194
|
-
});
|
|
195
|
-
});
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
encodeHttpRequest,
|
|
4
|
-
decodeHttpRequest,
|
|
5
|
-
encodeHttpResponse,
|
|
6
|
-
decodeHttpResponse,
|
|
7
|
-
encodeHttpResponseChunk,
|
|
8
|
-
decodeHttpResponseChunk,
|
|
9
|
-
} from '../src/proxy/request-codec.js';
|
|
10
|
-
import type {
|
|
11
|
-
SerializedHttpRequest,
|
|
12
|
-
SerializedHttpResponse,
|
|
13
|
-
SerializedHttpResponseChunk,
|
|
14
|
-
} from '../src/types/http.js';
|
|
15
|
-
|
|
16
|
-
describe('HTTP Request codec', () => {
|
|
17
|
-
it('should round-trip a basic request', () => {
|
|
18
|
-
const req: SerializedHttpRequest = {
|
|
19
|
-
requestId: 'req-123',
|
|
20
|
-
method: 'POST',
|
|
21
|
-
path: '/v1/chat/completions',
|
|
22
|
-
headers: { 'content-type': 'application/json', authorization: 'Bearer sk-test' },
|
|
23
|
-
body: new TextEncoder().encode('{"model":"gpt-4"}'),
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const encoded = encodeHttpRequest(req);
|
|
27
|
-
const decoded = decodeHttpRequest(encoded);
|
|
28
|
-
|
|
29
|
-
expect(decoded.requestId).toBe(req.requestId);
|
|
30
|
-
expect(decoded.method).toBe(req.method);
|
|
31
|
-
expect(decoded.path).toBe(req.path);
|
|
32
|
-
expect(decoded.headers).toEqual(req.headers);
|
|
33
|
-
expect(new TextDecoder().decode(decoded.body)).toBe('{"model":"gpt-4"}');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should handle empty headers', () => {
|
|
37
|
-
const req: SerializedHttpRequest = {
|
|
38
|
-
requestId: 'r',
|
|
39
|
-
method: 'GET',
|
|
40
|
-
path: '/',
|
|
41
|
-
headers: {},
|
|
42
|
-
body: new Uint8Array(0),
|
|
43
|
-
};
|
|
44
|
-
const decoded = decodeHttpRequest(encodeHttpRequest(req));
|
|
45
|
-
expect(decoded.headers).toEqual({});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should handle empty body', () => {
|
|
49
|
-
const req: SerializedHttpRequest = {
|
|
50
|
-
requestId: 'r',
|
|
51
|
-
method: 'GET',
|
|
52
|
-
path: '/health',
|
|
53
|
-
headers: {},
|
|
54
|
-
body: new Uint8Array(0),
|
|
55
|
-
};
|
|
56
|
-
const decoded = decodeHttpRequest(encodeHttpRequest(req));
|
|
57
|
-
expect(decoded.body.length).toBe(0);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('should handle large body', () => {
|
|
61
|
-
const largeBody = new Uint8Array(100_000);
|
|
62
|
-
largeBody.fill(0x42);
|
|
63
|
-
const req: SerializedHttpRequest = {
|
|
64
|
-
requestId: 'big',
|
|
65
|
-
method: 'POST',
|
|
66
|
-
path: '/upload',
|
|
67
|
-
headers: {},
|
|
68
|
-
body: largeBody,
|
|
69
|
-
};
|
|
70
|
-
const decoded = decodeHttpRequest(encodeHttpRequest(req));
|
|
71
|
-
expect(decoded.body.length).toBe(100_000);
|
|
72
|
-
expect(decoded.body[0]).toBe(0x42);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe('HTTP Response codec', () => {
|
|
77
|
-
it('should round-trip a basic response', () => {
|
|
78
|
-
const resp: SerializedHttpResponse = {
|
|
79
|
-
requestId: 'req-123',
|
|
80
|
-
statusCode: 200,
|
|
81
|
-
headers: { 'content-type': 'application/json' },
|
|
82
|
-
body: new TextEncoder().encode('{"result":"ok"}'),
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const decoded = decodeHttpResponse(encodeHttpResponse(resp));
|
|
86
|
-
|
|
87
|
-
expect(decoded.requestId).toBe(resp.requestId);
|
|
88
|
-
expect(decoded.statusCode).toBe(200);
|
|
89
|
-
expect(decoded.headers).toEqual(resp.headers);
|
|
90
|
-
expect(new TextDecoder().decode(decoded.body)).toBe('{"result":"ok"}');
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should handle 500 status code', () => {
|
|
94
|
-
const resp: SerializedHttpResponse = {
|
|
95
|
-
requestId: 'err',
|
|
96
|
-
statusCode: 500,
|
|
97
|
-
headers: {},
|
|
98
|
-
body: new TextEncoder().encode('Internal Server Error'),
|
|
99
|
-
};
|
|
100
|
-
const decoded = decodeHttpResponse(encodeHttpResponse(resp));
|
|
101
|
-
expect(decoded.statusCode).toBe(500);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
describe('HTTP Response Chunk codec', () => {
|
|
106
|
-
it('should round-trip a chunk with done=false', () => {
|
|
107
|
-
const chunk: SerializedHttpResponseChunk = {
|
|
108
|
-
requestId: 'req-123',
|
|
109
|
-
data: new TextEncoder().encode('data: {"text":"hello"}'),
|
|
110
|
-
done: false,
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const decoded = decodeHttpResponseChunk(encodeHttpResponseChunk(chunk));
|
|
114
|
-
|
|
115
|
-
expect(decoded.requestId).toBe('req-123');
|
|
116
|
-
expect(decoded.done).toBe(false);
|
|
117
|
-
expect(new TextDecoder().decode(decoded.data)).toBe('data: {"text":"hello"}');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should round-trip a chunk with done=true', () => {
|
|
121
|
-
const chunk: SerializedHttpResponseChunk = {
|
|
122
|
-
requestId: 'req-123',
|
|
123
|
-
data: new Uint8Array(0),
|
|
124
|
-
done: true,
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const decoded = decodeHttpResponseChunk(encodeHttpResponseChunk(chunk));
|
|
128
|
-
|
|
129
|
-
expect(decoded.requestId).toBe('req-123');
|
|
130
|
-
expect(decoded.done).toBe(true);
|
|
131
|
-
expect(decoded.data.length).toBe(0);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('should handle empty data with done=false', () => {
|
|
135
|
-
const chunk: SerializedHttpResponseChunk = {
|
|
136
|
-
requestId: 'x',
|
|
137
|
-
data: new Uint8Array(0),
|
|
138
|
-
done: false,
|
|
139
|
-
};
|
|
140
|
-
const decoded = decodeHttpResponseChunk(encodeHttpResponseChunk(chunk));
|
|
141
|
-
expect(decoded.done).toBe(false);
|
|
142
|
-
expect(decoded.data.length).toBe(0);
|
|
143
|
-
});
|
|
144
|
-
});
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
estimateTokensFromContentLength,
|
|
4
|
-
estimateTokensFromStreamBytes,
|
|
5
|
-
estimateTokens,
|
|
6
|
-
BYTES_PER_TOKEN,
|
|
7
|
-
MIN_REQUEST_TOKENS,
|
|
8
|
-
MIN_RESPONSE_TOKENS,
|
|
9
|
-
} from '../src/metering/token-counter.js';
|
|
10
|
-
|
|
11
|
-
describe('estimateTokensFromContentLength', () => {
|
|
12
|
-
it('should return MIN_REQUEST_TOKENS for null content-length (request)', () => {
|
|
13
|
-
expect(estimateTokensFromContentLength(null, 'openai', 'request')).toBe(MIN_REQUEST_TOKENS);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('should return MIN_RESPONSE_TOKENS for null content-length (response)', () => {
|
|
17
|
-
expect(estimateTokensFromContentLength(null, 'openai', 'response')).toBe(MIN_RESPONSE_TOKENS);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('should return minimum for zero content-length', () => {
|
|
21
|
-
expect(estimateTokensFromContentLength(0, 'openai', 'request')).toBe(MIN_REQUEST_TOKENS);
|
|
22
|
-
expect(estimateTokensFromContentLength(0, 'openai', 'response')).toBe(MIN_RESPONSE_TOKENS);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('should estimate tokens from bytes for openai', () => {
|
|
26
|
-
const bytes = 4000; // 4000 bytes / 4.0 bytes-per-token = 1000 tokens
|
|
27
|
-
const result = estimateTokensFromContentLength(bytes, 'openai', 'request');
|
|
28
|
-
expect(result).toBe(1000);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('should estimate tokens from bytes for anthropic', () => {
|
|
32
|
-
const bytes = 4200; // 4200 bytes / 4.2 bytes-per-token = 1000 tokens
|
|
33
|
-
const result = estimateTokensFromContentLength(bytes, 'anthropic', 'request');
|
|
34
|
-
expect(result).toBe(1000);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should use default ratio for unknown provider', () => {
|
|
38
|
-
const bytes = 4000;
|
|
39
|
-
const result = estimateTokensFromContentLength(bytes, 'custom', 'request');
|
|
40
|
-
expect(result).toBe(1000); // 4000 / 4.0 = 1000
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should enforce minimum for small content', () => {
|
|
44
|
-
// 10 bytes / 4.0 = 3 tokens, but minimum is MIN_REQUEST_TOKENS
|
|
45
|
-
const result = estimateTokensFromContentLength(10, 'openai', 'request');
|
|
46
|
-
expect(result).toBe(MIN_REQUEST_TOKENS);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should ceil the result', () => {
|
|
50
|
-
// 5 bytes / 4.0 = 1.25, ceil = 2, but min = 100
|
|
51
|
-
// Let's use a larger value: 4001 / 4.0 = 1000.25, ceil = 1001
|
|
52
|
-
const result = estimateTokensFromContentLength(4001, 'openai', 'request');
|
|
53
|
-
expect(result).toBe(1001);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('estimateTokensFromStreamBytes', () => {
|
|
58
|
-
it('should return MIN_RESPONSE_TOKENS for zero bytes', () => {
|
|
59
|
-
expect(estimateTokensFromStreamBytes(0, 'openai')).toBe(MIN_RESPONSE_TOKENS);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('should account for SSE overhead (0.82 factor)', () => {
|
|
63
|
-
const totalBytes = 10000;
|
|
64
|
-
const contentBytes = totalBytes * 0.82;
|
|
65
|
-
const expected = Math.max(Math.ceil(contentBytes / 4.0), MIN_RESPONSE_TOKENS);
|
|
66
|
-
expect(estimateTokensFromStreamBytes(totalBytes, 'openai')).toBe(expected);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should use provider-specific ratio', () => {
|
|
70
|
-
const totalBytes = 10000;
|
|
71
|
-
const contentBytes = totalBytes * 0.82;
|
|
72
|
-
const ratio = BYTES_PER_TOKEN['anthropic']!;
|
|
73
|
-
const expected = Math.max(Math.ceil(contentBytes / ratio), MIN_RESPONSE_TOKENS);
|
|
74
|
-
expect(estimateTokensFromStreamBytes(totalBytes, 'anthropic')).toBe(expected);
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
describe('estimateTokens', () => {
|
|
79
|
-
it('should use content-length for non-streaming response', () => {
|
|
80
|
-
const result = estimateTokens(4000, 8000, 'openai', false);
|
|
81
|
-
expect(result.method).toBe('content-length');
|
|
82
|
-
expect(result.confidence).toBe('high');
|
|
83
|
-
expect(result.inputTokens).toBe(1000);
|
|
84
|
-
expect(result.outputTokens).toBe(2000);
|
|
85
|
-
expect(result.totalTokens).toBe(3000);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('should use chunk-accumulation for streaming response', () => {
|
|
89
|
-
const result = estimateTokens(4000, null, 'openai', true, 10000);
|
|
90
|
-
expect(result.method).toBe('chunk-accumulation');
|
|
91
|
-
expect(result.confidence).toBe('medium');
|
|
92
|
-
expect(result.inputTokens).toBe(1000);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('should use fallback when no response info available', () => {
|
|
96
|
-
const result = estimateTokens(null, null, 'openai', false);
|
|
97
|
-
expect(result.method).toBe('fallback');
|
|
98
|
-
expect(result.confidence).toBe('low');
|
|
99
|
-
expect(result.inputTokens).toBe(MIN_REQUEST_TOKENS);
|
|
100
|
-
expect(result.outputTokens).toBe(MIN_RESPONSE_TOKENS);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('should compute totalTokens as input + output', () => {
|
|
104
|
-
const result = estimateTokens(4000, 4000, 'openai', false);
|
|
105
|
-
expect(result.totalTokens).toBe(result.inputTokens + result.outputTokens);
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe('constants', () => {
|
|
110
|
-
it('should have BYTES_PER_TOKEN for known providers', () => {
|
|
111
|
-
expect(BYTES_PER_TOKEN['anthropic']).toBeDefined();
|
|
112
|
-
expect(BYTES_PER_TOKEN['openai']).toBeDefined();
|
|
113
|
-
expect(BYTES_PER_TOKEN['google']).toBeDefined();
|
|
114
|
-
expect(BYTES_PER_TOKEN['moonshot']).toBeDefined();
|
|
115
|
-
expect(BYTES_PER_TOKEN['default']).toBeDefined();
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('should have reasonable MIN token values', () => {
|
|
119
|
-
expect(MIN_REQUEST_TOKENS).toBe(100);
|
|
120
|
-
expect(MIN_RESPONSE_TOKENS).toBe(10);
|
|
121
|
-
});
|
|
122
|
-
});
|
package/tsconfig.json
DELETED