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