@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.
Files changed (140) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +7 -5
  3. package/dist/discovery/http-metadata-resolver.d.ts +6 -0
  4. package/dist/discovery/http-metadata-resolver.d.ts.map +1 -1
  5. package/dist/discovery/http-metadata-resolver.js +32 -4
  6. package/dist/discovery/http-metadata-resolver.js.map +1 -1
  7. package/dist/discovery/peer-lookup.d.ts +1 -0
  8. package/dist/discovery/peer-lookup.d.ts.map +1 -1
  9. package/dist/discovery/peer-lookup.js +10 -25
  10. package/dist/discovery/peer-lookup.js.map +1 -1
  11. package/dist/index.d.ts +2 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/interfaces/seller-provider.d.ts +13 -1
  16. package/dist/interfaces/seller-provider.d.ts.map +1 -1
  17. package/dist/node.d.ts +13 -3
  18. package/dist/node.d.ts.map +1 -1
  19. package/dist/node.js +146 -21
  20. package/dist/node.js.map +1 -1
  21. package/dist/proxy/proxy-mux.d.ts +3 -1
  22. package/dist/proxy/proxy-mux.d.ts.map +1 -1
  23. package/dist/proxy/proxy-mux.js +9 -5
  24. package/dist/proxy/proxy-mux.js.map +1 -1
  25. package/dist/types/http.d.ts +1 -0
  26. package/dist/types/http.d.ts.map +1 -1
  27. package/dist/types/http.js +1 -1
  28. package/dist/types/http.js.map +1 -1
  29. package/package.json +14 -10
  30. package/contracts/AntseedEscrow.sol +0 -310
  31. package/contracts/MockUSDC.sol +0 -64
  32. package/contracts/README.md +0 -102
  33. package/src/config/encryption.test.ts +0 -49
  34. package/src/config/encryption.ts +0 -53
  35. package/src/config/plugin-config-manager.test.ts +0 -92
  36. package/src/config/plugin-config-manager.ts +0 -153
  37. package/src/config/plugin-loader.ts +0 -90
  38. package/src/discovery/announcer.ts +0 -169
  39. package/src/discovery/bootstrap.ts +0 -57
  40. package/src/discovery/default-metadata-resolver.ts +0 -18
  41. package/src/discovery/dht-health.ts +0 -136
  42. package/src/discovery/dht-node.ts +0 -191
  43. package/src/discovery/http-metadata-resolver.ts +0 -47
  44. package/src/discovery/index.ts +0 -15
  45. package/src/discovery/metadata-codec.ts +0 -453
  46. package/src/discovery/metadata-resolver.ts +0 -7
  47. package/src/discovery/metadata-server.ts +0 -73
  48. package/src/discovery/metadata-validator.ts +0 -172
  49. package/src/discovery/peer-lookup.ts +0 -122
  50. package/src/discovery/peer-metadata.ts +0 -34
  51. package/src/discovery/peer-selector.ts +0 -134
  52. package/src/discovery/profile-manager.ts +0 -131
  53. package/src/discovery/profile-search.ts +0 -100
  54. package/src/discovery/reputation-verifier.ts +0 -54
  55. package/src/index.ts +0 -61
  56. package/src/interfaces/buyer-router.ts +0 -21
  57. package/src/interfaces/plugin.ts +0 -36
  58. package/src/interfaces/seller-provider.ts +0 -81
  59. package/src/metering/index.ts +0 -6
  60. package/src/metering/receipt-generator.ts +0 -105
  61. package/src/metering/receipt-verifier.ts +0 -102
  62. package/src/metering/session-tracker.ts +0 -145
  63. package/src/metering/storage.ts +0 -600
  64. package/src/metering/token-counter.ts +0 -127
  65. package/src/metering/usage-aggregator.ts +0 -236
  66. package/src/node.ts +0 -1698
  67. package/src/p2p/connection-auth.ts +0 -152
  68. package/src/p2p/connection-manager.ts +0 -916
  69. package/src/p2p/handshake.ts +0 -162
  70. package/src/p2p/ice-config.ts +0 -59
  71. package/src/p2p/identity.ts +0 -110
  72. package/src/p2p/index.ts +0 -11
  73. package/src/p2p/keepalive.ts +0 -118
  74. package/src/p2p/message-protocol.ts +0 -171
  75. package/src/p2p/nat-traversal.ts +0 -169
  76. package/src/p2p/payment-codec.ts +0 -165
  77. package/src/p2p/payment-mux.ts +0 -153
  78. package/src/p2p/reconnect.ts +0 -117
  79. package/src/payments/balance-manager.ts +0 -77
  80. package/src/payments/buyer-payment-manager.ts +0 -414
  81. package/src/payments/disputes.ts +0 -72
  82. package/src/payments/evm/escrow-client.ts +0 -263
  83. package/src/payments/evm/keypair.ts +0 -31
  84. package/src/payments/evm/signatures.ts +0 -103
  85. package/src/payments/evm/wallet.ts +0 -42
  86. package/src/payments/index.ts +0 -50
  87. package/src/payments/settlement.ts +0 -40
  88. package/src/payments/types.ts +0 -79
  89. package/src/proxy/index.ts +0 -3
  90. package/src/proxy/provider-detection.ts +0 -78
  91. package/src/proxy/proxy-mux.ts +0 -173
  92. package/src/proxy/request-codec.ts +0 -294
  93. package/src/reputation/index.ts +0 -6
  94. package/src/reputation/rating-manager.ts +0 -118
  95. package/src/reputation/report-manager.ts +0 -91
  96. package/src/reputation/trust-engine.ts +0 -120
  97. package/src/reputation/trust-score.ts +0 -74
  98. package/src/reputation/uptime-tracker.ts +0 -155
  99. package/src/routing/default-router.ts +0 -75
  100. package/src/types/bittorrent-dht.d.ts +0 -19
  101. package/src/types/buyer.ts +0 -37
  102. package/src/types/capability.ts +0 -34
  103. package/src/types/connection.ts +0 -29
  104. package/src/types/http.ts +0 -20
  105. package/src/types/index.ts +0 -14
  106. package/src/types/metering.ts +0 -175
  107. package/src/types/nat-api.d.ts +0 -29
  108. package/src/types/peer-profile.ts +0 -25
  109. package/src/types/peer.ts +0 -62
  110. package/src/types/plugin-config.ts +0 -31
  111. package/src/types/protocol.ts +0 -162
  112. package/src/types/provider.ts +0 -40
  113. package/src/types/rating.ts +0 -23
  114. package/src/types/report.ts +0 -30
  115. package/src/types/seller.ts +0 -38
  116. package/src/types/staking.ts +0 -23
  117. package/src/utils/debug.ts +0 -30
  118. package/src/utils/hex.ts +0 -14
  119. package/tests/balance-manager.test.ts +0 -156
  120. package/tests/bootstrap.test.ts +0 -108
  121. package/tests/buyer-payment-manager.test.ts +0 -358
  122. package/tests/connection-auth.test.ts +0 -87
  123. package/tests/default-router.test.ts +0 -148
  124. package/tests/evm-keypair.test.ts +0 -173
  125. package/tests/identity.test.ts +0 -133
  126. package/tests/message-protocol.test.ts +0 -212
  127. package/tests/metadata-codec.test.ts +0 -165
  128. package/tests/metadata-validator.test.ts +0 -261
  129. package/tests/metering-storage.test.ts +0 -244
  130. package/tests/payment-codec.test.ts +0 -95
  131. package/tests/payment-mux.test.ts +0 -191
  132. package/tests/peer-selector.test.ts +0 -184
  133. package/tests/provider-detection.test.ts +0 -107
  134. package/tests/proxy-mux-security.test.ts +0 -38
  135. package/tests/receipt.test.ts +0 -215
  136. package/tests/reputation-integration.test.ts +0 -195
  137. package/tests/request-codec.test.ts +0 -144
  138. package/tests/token-counter.test.ts +0 -122
  139. package/tsconfig.json +0 -9
  140. package/vitest.config.ts +0 -7
@@ -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
@@ -1,9 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "rootDir": "src"
6
- },
7
- "include": ["src/**/*.ts"],
8
- "exclude": ["node_modules", "dist", "**/*.test.ts"]
9
- }
package/vitest.config.ts DELETED
@@ -1,7 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- include: ['tests/**/*.test.ts', 'src/**/*.test.ts'],
6
- },
7
- });