@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,165 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { encodeMetadata, decodeMetadata, encodeMetadataForSigning } from '../src/discovery/metadata-codec.js';
3
- import type { PeerMetadata } from '../src/discovery/peer-metadata.js';
4
-
5
- function makeMetadata(overrides?: Partial<PeerMetadata>): PeerMetadata {
6
- return {
7
- peerId: 'a'.repeat(64) as any,
8
- version: 2,
9
- providers: [
10
- {
11
- provider: 'anthropic',
12
- models: ['claude-3-opus', 'claude-3-sonnet'],
13
- defaultPricing: {
14
- inputUsdPerMillion: 15,
15
- outputUsdPerMillion: 75,
16
- },
17
- modelPricing: {
18
- 'claude-3-opus': {
19
- inputUsdPerMillion: 18,
20
- outputUsdPerMillion: 90,
21
- },
22
- },
23
- maxConcurrency: 10,
24
- currentLoad: 3,
25
- },
26
- ],
27
- region: 'us-east-1',
28
- timestamp: 1700000000000,
29
- signature: 'b'.repeat(128),
30
- ...overrides,
31
- };
32
- }
33
-
34
- describe('encodeMetadata / decodeMetadata', () => {
35
- it('should round-trip a basic metadata object', () => {
36
- const original = makeMetadata();
37
- const encoded = encodeMetadata(original);
38
- const decoded = decodeMetadata(encoded);
39
-
40
- expect(decoded.version).toBe(original.version);
41
- expect(decoded.peerId).toBe(original.peerId);
42
- expect(decoded.region).toBe(original.region);
43
- expect(decoded.timestamp).toBe(original.timestamp);
44
- expect(decoded.signature).toBe(original.signature);
45
- expect(decoded.providers).toHaveLength(1);
46
- expect(decoded.providers[0]!.provider).toBe('anthropic');
47
- expect(decoded.providers[0]!.models).toEqual(['claude-3-opus', 'claude-3-sonnet']);
48
- expect(decoded.providers[0]!.maxConcurrency).toBe(10);
49
- expect(decoded.providers[0]!.currentLoad).toBe(3);
50
- });
51
-
52
- it('should handle float32 precision for prices', () => {
53
- const original = makeMetadata();
54
- const encoded = encodeMetadata(original);
55
- const decoded = decodeMetadata(encoded);
56
- // Float32 has limited precision — allow small delta
57
- expect(decoded.providers[0]!.defaultPricing.inputUsdPerMillion).toBeCloseTo(15, 3);
58
- expect(decoded.providers[0]!.defaultPricing.outputUsdPerMillion).toBeCloseTo(75, 3);
59
- expect(decoded.providers[0]!.modelPricing?.['claude-3-opus']?.inputUsdPerMillion).toBeCloseTo(18, 3);
60
- expect(decoded.providers[0]!.modelPricing?.['claude-3-opus']?.outputUsdPerMillion).toBeCloseTo(90, 3);
61
- });
62
-
63
- it('should round-trip multiple providers', () => {
64
- const original = makeMetadata({
65
- providers: [
66
- {
67
- provider: 'openai',
68
- models: ['gpt-4'],
69
- defaultPricing: {
70
- inputUsdPerMillion: 10,
71
- outputUsdPerMillion: 30,
72
- },
73
- maxConcurrency: 5,
74
- currentLoad: 0,
75
- },
76
- {
77
- provider: 'anthropic',
78
- models: ['claude-3-haiku'],
79
- defaultPricing: {
80
- inputUsdPerMillion: 1,
81
- outputUsdPerMillion: 5,
82
- },
83
- modelPricing: {
84
- 'claude-3-haiku': {
85
- inputUsdPerMillion: 0.9,
86
- outputUsdPerMillion: 4.5,
87
- },
88
- },
89
- maxConcurrency: 20,
90
- currentLoad: 10,
91
- },
92
- ],
93
- });
94
- const decoded = decodeMetadata(encodeMetadata(original));
95
- expect(decoded.providers).toHaveLength(2);
96
- expect(decoded.providers[0]!.provider).toBe('openai');
97
- expect(decoded.providers[1]!.provider).toBe('anthropic');
98
- });
99
-
100
- it('should round-trip zero providers', () => {
101
- const original = makeMetadata({ providers: [] });
102
- const decoded = decodeMetadata(encodeMetadata(original));
103
- expect(decoded.providers).toHaveLength(0);
104
- });
105
-
106
- it('should round-trip empty models list', () => {
107
- const original = makeMetadata({
108
- providers: [
109
- {
110
- provider: 'test',
111
- models: [],
112
- defaultPricing: {
113
- inputUsdPerMillion: 0,
114
- outputUsdPerMillion: 0,
115
- },
116
- maxConcurrency: 1,
117
- currentLoad: 0,
118
- },
119
- ],
120
- });
121
- const decoded = decodeMetadata(encodeMetadata(original));
122
- expect(decoded.providers[0]!.models).toEqual([]);
123
- });
124
-
125
- it('should decode offerings and optional trailer fields after v2 provider pricing payload', () => {
126
- const original = makeMetadata({
127
- offerings: [
128
- {
129
- capability: 'skill',
130
- name: 'summarize',
131
- description: 'Summarize text',
132
- pricing: { unit: 'request', pricePerUnit: 0.1, currency: 'USD' },
133
- models: ['claude-3-sonnet'],
134
- },
135
- ],
136
- evmAddress: '0x1111111111111111111111111111111111111111',
137
- onChainReputation: 88,
138
- onChainSessionCount: 123,
139
- onChainDisputeCount: 2,
140
- });
141
- const decoded = decodeMetadata(encodeMetadata(original));
142
- expect(decoded.offerings?.[0]?.name).toBe('summarize');
143
- expect(decoded.evmAddress).toBe('0x1111111111111111111111111111111111111111');
144
- expect(decoded.onChainReputation).toBe(88);
145
- expect(decoded.onChainSessionCount).toBe(123);
146
- expect(decoded.onChainDisputeCount).toBe(2);
147
- });
148
- });
149
-
150
- describe('encodeMetadataForSigning', () => {
151
- it('should produce a shorter buffer than encodeMetadata (no signature)', () => {
152
- const metadata = makeMetadata();
153
- const forSigning = encodeMetadataForSigning(metadata);
154
- const full = encodeMetadata(metadata);
155
- // Full includes 64 bytes of signature
156
- expect(full.length).toBe(forSigning.length + 64);
157
- });
158
-
159
- it('should produce deterministic output for the same input', () => {
160
- const metadata = makeMetadata();
161
- const a = encodeMetadataForSigning(metadata);
162
- const b = encodeMetadataForSigning(metadata);
163
- expect(a).toEqual(b);
164
- });
165
- });
@@ -1,261 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import {
3
- validateMetadata,
4
- MAX_METADATA_SIZE,
5
- MAX_PROVIDERS,
6
- MAX_MODELS_PER_PROVIDER,
7
- MAX_MODEL_NAME_LENGTH,
8
- MAX_REGION_LENGTH,
9
- } from '../src/discovery/metadata-validator.js';
10
- import type { PeerMetadata } from '../src/discovery/peer-metadata.js';
11
-
12
- function validMetadata(overrides?: Partial<PeerMetadata>): PeerMetadata {
13
- return {
14
- peerId: 'a'.repeat(64) as any,
15
- version: 2,
16
- providers: [
17
- {
18
- provider: 'anthropic',
19
- models: ['claude-3-opus'],
20
- defaultPricing: {
21
- inputUsdPerMillion: 15,
22
- outputUsdPerMillion: 75,
23
- },
24
- maxConcurrency: 10,
25
- currentLoad: 3,
26
- },
27
- ],
28
- region: 'us-east-1',
29
- timestamp: Date.now(),
30
- signature: 'b'.repeat(128),
31
- ...overrides,
32
- };
33
- }
34
-
35
- describe('validateMetadata', () => {
36
- it('should return no errors for valid metadata', () => {
37
- const errors = validateMetadata(validMetadata());
38
- expect(errors).toEqual([]);
39
- });
40
-
41
- it('should reject wrong version', () => {
42
- const errors = validateMetadata(validMetadata({ version: 99 }));
43
- expect(errors.some((e) => e.field === 'version')).toBe(true);
44
- });
45
-
46
- it('should reject invalid peerId (too short)', () => {
47
- const errors = validateMetadata(validMetadata({ peerId: 'abc' as any }));
48
- expect(errors.some((e) => e.field === 'peerId')).toBe(true);
49
- });
50
-
51
- it('should reject invalid peerId (uppercase)', () => {
52
- const errors = validateMetadata(validMetadata({ peerId: 'A'.repeat(64) as any }));
53
- expect(errors.some((e) => e.field === 'peerId')).toBe(true);
54
- });
55
-
56
- it('should reject empty region', () => {
57
- const errors = validateMetadata(validMetadata({ region: '' }));
58
- expect(errors.some((e) => e.field === 'region')).toBe(true);
59
- });
60
-
61
- it('should reject region exceeding max length', () => {
62
- const errors = validateMetadata(validMetadata({ region: 'x'.repeat(MAX_REGION_LENGTH + 1) }));
63
- expect(errors.some((e) => e.field === 'region')).toBe(true);
64
- });
65
-
66
- it('should reject non-positive timestamp', () => {
67
- const errors = validateMetadata(validMetadata({ timestamp: 0 }));
68
- expect(errors.some((e) => e.field === 'timestamp')).toBe(true);
69
- });
70
-
71
- it('should reject NaN timestamp', () => {
72
- const errors = validateMetadata(validMetadata({ timestamp: NaN }));
73
- expect(errors.some((e) => e.field === 'timestamp')).toBe(true);
74
- });
75
-
76
- it('should reject zero providers', () => {
77
- const errors = validateMetadata(validMetadata({ providers: [] }));
78
- expect(errors.some((e) => e.field === 'providers')).toBe(true);
79
- });
80
-
81
- it('should reject too many providers', () => {
82
- const providers = Array.from({ length: MAX_PROVIDERS + 1 }, (_, i) => ({
83
- provider: `p${i}`,
84
- models: ['m'],
85
- defaultPricing: {
86
- inputUsdPerMillion: 1,
87
- outputUsdPerMillion: 1,
88
- },
89
- maxConcurrency: 1,
90
- currentLoad: 0,
91
- }));
92
- const errors = validateMetadata(validMetadata({ providers }));
93
- expect(errors.some((e) => e.field === 'providers')).toBe(true);
94
- });
95
-
96
- it('should reject too many models per provider', () => {
97
- const models = Array.from({ length: MAX_MODELS_PER_PROVIDER + 1 }, (_, i) => `model-${i}`);
98
- const errors = validateMetadata(
99
- validMetadata({
100
- providers: [
101
- {
102
- provider: 'test',
103
- models,
104
- defaultPricing: {
105
- inputUsdPerMillion: 1,
106
- outputUsdPerMillion: 1,
107
- },
108
- maxConcurrency: 1,
109
- currentLoad: 0,
110
- },
111
- ],
112
- })
113
- );
114
- expect(errors.some((e) => e.field.includes('models'))).toBe(true);
115
- });
116
-
117
- it('should reject model name exceeding max length', () => {
118
- const errors = validateMetadata(
119
- validMetadata({
120
- providers: [
121
- {
122
- provider: 'test',
123
- models: ['x'.repeat(MAX_MODEL_NAME_LENGTH + 1)],
124
- defaultPricing: {
125
- inputUsdPerMillion: 1,
126
- outputUsdPerMillion: 1,
127
- },
128
- maxConcurrency: 1,
129
- currentLoad: 0,
130
- },
131
- ],
132
- })
133
- );
134
- expect(errors.some((e) => e.field.includes('models'))).toBe(true);
135
- });
136
-
137
- it('should reject negative default input price', () => {
138
- const errors = validateMetadata(
139
- validMetadata({
140
- providers: [
141
- {
142
- provider: 'test',
143
- models: ['m'],
144
- defaultPricing: {
145
- inputUsdPerMillion: -1,
146
- outputUsdPerMillion: 1,
147
- },
148
- maxConcurrency: 1,
149
- currentLoad: 0,
150
- },
151
- ],
152
- })
153
- );
154
- expect(errors.some((e) => e.field.includes('defaultPricing.inputUsdPerMillion'))).toBe(true);
155
- });
156
-
157
- it('should reject negative default output price', () => {
158
- const errors = validateMetadata(
159
- validMetadata({
160
- providers: [
161
- {
162
- provider: 'test',
163
- models: ['m'],
164
- defaultPricing: {
165
- inputUsdPerMillion: 1,
166
- outputUsdPerMillion: -1,
167
- },
168
- maxConcurrency: 1,
169
- currentLoad: 0,
170
- },
171
- ],
172
- })
173
- );
174
- expect(errors.some((e) => e.field.includes('defaultPricing.outputUsdPerMillion'))).toBe(true);
175
- });
176
-
177
- it('should reject model pricing entries with missing output half', () => {
178
- const errors = validateMetadata(
179
- validMetadata({
180
- providers: [
181
- {
182
- provider: 'test',
183
- models: ['m'],
184
- defaultPricing: {
185
- inputUsdPerMillion: 1,
186
- outputUsdPerMillion: 1,
187
- },
188
- modelPricing: {
189
- m: {
190
- inputUsdPerMillion: 2,
191
- } as any,
192
- } as any,
193
- maxConcurrency: 1,
194
- currentLoad: 0,
195
- },
196
- ],
197
- })
198
- );
199
- expect(errors.some((e) => e.field.includes('modelPricing.m.outputUsdPerMillion'))).toBe(true);
200
- });
201
-
202
- it('should reject maxConcurrency < 1', () => {
203
- const errors = validateMetadata(
204
- validMetadata({
205
- providers: [
206
- {
207
- provider: 'test',
208
- models: ['m'],
209
- defaultPricing: {
210
- inputUsdPerMillion: 1,
211
- outputUsdPerMillion: 1,
212
- },
213
- maxConcurrency: 0,
214
- currentLoad: 0,
215
- },
216
- ],
217
- })
218
- );
219
- expect(errors.some((e) => e.field.includes('maxConcurrency'))).toBe(true);
220
- });
221
-
222
- it('should reject currentLoad > maxConcurrency', () => {
223
- const errors = validateMetadata(
224
- validMetadata({
225
- providers: [
226
- {
227
- provider: 'test',
228
- models: ['m'],
229
- defaultPricing: {
230
- inputUsdPerMillion: 1,
231
- outputUsdPerMillion: 1,
232
- },
233
- maxConcurrency: 5,
234
- currentLoad: 6,
235
- },
236
- ],
237
- })
238
- );
239
- expect(errors.some((e) => e.field.includes('currentLoad'))).toBe(true);
240
- });
241
-
242
- it('should reject invalid signature format', () => {
243
- const errors = validateMetadata(validMetadata({ signature: 'xyz' }));
244
- expect(errors.some((e) => e.field === 'signature')).toBe(true);
245
- });
246
-
247
- it('should reject signature with uppercase hex', () => {
248
- const errors = validateMetadata(validMetadata({ signature: 'B'.repeat(128) }));
249
- expect(errors.some((e) => e.field === 'signature')).toBe(true);
250
- });
251
- });
252
-
253
- describe('constants', () => {
254
- it('should export reasonable constant values', () => {
255
- expect(MAX_METADATA_SIZE).toBe(1000);
256
- expect(MAX_PROVIDERS).toBe(10);
257
- expect(MAX_MODELS_PER_PROVIDER).toBe(20);
258
- expect(MAX_MODEL_NAME_LENGTH).toBe(64);
259
- expect(MAX_REGION_LENGTH).toBe(32);
260
- });
261
- });
@@ -1,244 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { MeteringStorage } from '../src/metering/storage.js';
3
- import type {
4
- MeteringEvent,
5
- UsageReceipt,
6
- ReceiptVerification,
7
- SessionMetrics,
8
- TokenCount,
9
- } from '../src/types/metering.js';
10
-
11
- let storage: MeteringStorage;
12
-
13
- function makeTokens(total: number): TokenCount {
14
- return {
15
- inputTokens: Math.floor(total * 0.6),
16
- outputTokens: Math.ceil(total * 0.4),
17
- totalTokens: total,
18
- method: 'content-length',
19
- confidence: 'high',
20
- };
21
- }
22
-
23
- function makeEvent(overrides?: Partial<MeteringEvent>): MeteringEvent {
24
- return {
25
- eventId: `evt-${Math.random().toString(36).slice(2)}`,
26
- sessionId: 'session-1',
27
- timestamp: Date.now(),
28
- provider: 'openai',
29
- sellerPeerId: 'seller-1',
30
- buyerPeerId: 'buyer-1',
31
- tokens: makeTokens(1000),
32
- latencyMs: 150,
33
- statusCode: 200,
34
- wasStreaming: false,
35
- ...overrides,
36
- };
37
- }
38
-
39
- function makeReceipt(overrides?: Partial<UsageReceipt>): UsageReceipt {
40
- return {
41
- receiptId: `rcpt-${Math.random().toString(36).slice(2)}`,
42
- sessionId: 'session-1',
43
- eventId: 'evt-1',
44
- timestamp: Date.now(),
45
- provider: 'openai',
46
- sellerPeerId: 'seller-1',
47
- buyerPeerId: 'buyer-1',
48
- tokens: makeTokens(1000),
49
- unitPriceCentsPerThousandTokens: 10,
50
- costCents: 10,
51
- signature: 'a'.repeat(128),
52
- ...overrides,
53
- };
54
- }
55
-
56
- function makeVerification(overrides?: Partial<ReceiptVerification>): ReceiptVerification {
57
- return {
58
- receiptId: `rcpt-${Math.random().toString(36).slice(2)}`,
59
- signatureValid: true,
60
- buyerTokenEstimate: makeTokens(1000),
61
- sellerTokenEstimate: makeTokens(1050),
62
- tokenDifference: 50,
63
- percentageDifference: 4.76,
64
- disputed: false,
65
- verifiedAt: Date.now(),
66
- ...overrides,
67
- };
68
- }
69
-
70
- function makeSession(overrides?: Partial<SessionMetrics>): SessionMetrics {
71
- return {
72
- sessionId: `sess-${Math.random().toString(36).slice(2)}`,
73
- sellerPeerId: 'seller-1',
74
- buyerPeerId: 'buyer-1',
75
- provider: 'openai',
76
- startedAt: Date.now(),
77
- endedAt: null,
78
- totalRequests: 5,
79
- totalTokens: 5000,
80
- totalCostCents: 50,
81
- avgLatencyMs: 200,
82
- peerSwitches: 0,
83
- disputedReceipts: 0,
84
- ...overrides,
85
- };
86
- }
87
-
88
- beforeEach(() => {
89
- storage = new MeteringStorage(':memory:');
90
- });
91
-
92
- afterEach(() => {
93
- storage.close();
94
- });
95
-
96
- describe('MeteringStorage — Events', () => {
97
- it('should insert and retrieve events by session', () => {
98
- const event = makeEvent({ sessionId: 'sess-a' });
99
- storage.insertEvent(event);
100
-
101
- const events = storage.getEventsBySession('sess-a');
102
- expect(events).toHaveLength(1);
103
- expect(events[0]!.eventId).toBe(event.eventId);
104
- expect(events[0]!.tokens.totalTokens).toBe(1000);
105
- expect(events[0]!.wasStreaming).toBe(false);
106
- });
107
-
108
- it('should retrieve events by time range', () => {
109
- const now = Date.now();
110
- storage.insertEvent(makeEvent({ eventId: 'e1', timestamp: now - 2000 }));
111
- storage.insertEvent(makeEvent({ eventId: 'e2', timestamp: now - 500 }));
112
- storage.insertEvent(makeEvent({ eventId: 'e3', timestamp: now + 1000 }));
113
-
114
- const events = storage.getEventsByTimeRange(now - 3000, now);
115
- expect(events).toHaveLength(2);
116
- });
117
-
118
- it('should return empty for non-existent session', () => {
119
- expect(storage.getEventsBySession('nonexistent')).toEqual([]);
120
- });
121
- });
122
-
123
- describe('MeteringStorage — Receipts', () => {
124
- it('should insert and retrieve receipts by session', () => {
125
- const receipt = makeReceipt({ sessionId: 'sess-b' });
126
- storage.insertReceipt(receipt);
127
-
128
- const receipts = storage.getReceiptsBySession('sess-b');
129
- expect(receipts).toHaveLength(1);
130
- expect(receipts[0]!.costCents).toBe(10);
131
- expect(receipts[0]!.signature).toBe('a'.repeat(128));
132
- });
133
-
134
- it('should calculate total cost in a time range', () => {
135
- const now = Date.now();
136
- storage.insertReceipt(makeReceipt({ receiptId: 'r1', timestamp: now - 500, costCents: 10 }));
137
- storage.insertReceipt(makeReceipt({ receiptId: 'r2', timestamp: now - 200, costCents: 20 }));
138
- storage.insertReceipt(makeReceipt({ receiptId: 'r3', timestamp: now + 1000, costCents: 100 }));
139
-
140
- const total = storage.getTotalCost(now - 1000, now);
141
- expect(total).toBe(30);
142
- });
143
-
144
- it('should return 0 total cost for empty range', () => {
145
- expect(storage.getTotalCost(0, 1)).toBe(0);
146
- });
147
- });
148
-
149
- describe('MeteringStorage — Verifications', () => {
150
- it('should insert and retrieve disputed verifications', () => {
151
- const now = Date.now();
152
- storage.insertVerification(
153
- makeVerification({ receiptId: 'v1', disputed: true, verifiedAt: now - 100 })
154
- );
155
- storage.insertVerification(
156
- makeVerification({ receiptId: 'v2', disputed: false, verifiedAt: now - 50 })
157
- );
158
-
159
- const disputed = storage.getDisputedVerifications(now - 200, now + 200);
160
- expect(disputed).toHaveLength(1);
161
- expect(disputed[0]!.receiptId).toBe('v1');
162
- expect(disputed[0]!.disputed).toBe(true);
163
- });
164
- });
165
-
166
- describe('MeteringStorage — Sessions', () => {
167
- it('should upsert and retrieve a session', () => {
168
- const session = makeSession({ sessionId: 'sess-x' });
169
- storage.upsertSession(session);
170
-
171
- const retrieved = storage.getSession('sess-x');
172
- expect(retrieved).not.toBeNull();
173
- expect(retrieved!.totalRequests).toBe(5);
174
- expect(retrieved!.endedAt).toBeNull();
175
- });
176
-
177
- it('should update session on upsert with same ID', () => {
178
- const session = makeSession({ sessionId: 'sess-y', totalRequests: 1 });
179
- storage.upsertSession(session);
180
-
181
- session.totalRequests = 10;
182
- session.endedAt = Date.now();
183
- storage.upsertSession(session);
184
-
185
- const retrieved = storage.getSession('sess-y');
186
- expect(retrieved!.totalRequests).toBe(10);
187
- expect(retrieved!.endedAt).not.toBeNull();
188
- });
189
-
190
- it('should return null for non-existent session', () => {
191
- expect(storage.getSession('nonexistent')).toBeNull();
192
- });
193
-
194
- it('should get session summary for a time range', () => {
195
- const now = Date.now();
196
- storage.upsertSession(makeSession({ sessionId: 's1', startedAt: now - 500, totalCostCents: 10, totalTokens: 1000, totalRequests: 5 }));
197
- storage.upsertSession(makeSession({ sessionId: 's2', startedAt: now - 200, totalCostCents: 20, totalTokens: 2000, totalRequests: 3 }));
198
-
199
- const summary = storage.getSessionSummary(now - 1000, now);
200
- expect(summary.sessionCount).toBe(2);
201
- expect(summary.totalCostCents).toBe(30);
202
- expect(summary.totalTokens).toBe(3000);
203
- expect(summary.totalRequests).toBe(8);
204
- });
205
-
206
- it('should get event token summary', () => {
207
- const now = Date.now();
208
- storage.insertEvent(makeEvent({ eventId: 'e1', timestamp: now - 500, tokens: makeTokens(1000) }));
209
- storage.insertEvent(makeEvent({ eventId: 'e2', timestamp: now - 200, tokens: makeTokens(2000) }));
210
-
211
- const summary = storage.getEventTokenSummary(now - 1000, now);
212
- expect(summary.totalRequests).toBe(2);
213
- expect(summary.totalTokens).toBe(3000);
214
- });
215
- });
216
-
217
- describe('MeteringStorage — Pruning', () => {
218
- it('should delete data older than given timestamp', () => {
219
- const now = Date.now();
220
- const old = now - 100000;
221
- const recent = now - 100;
222
-
223
- storage.insertEvent(makeEvent({ eventId: 'old-e', timestamp: old }));
224
- storage.insertEvent(makeEvent({ eventId: 'new-e', timestamp: recent }));
225
- storage.insertReceipt(makeReceipt({ receiptId: 'old-r', timestamp: old }));
226
- storage.insertReceipt(makeReceipt({ receiptId: 'new-r', timestamp: recent }));
227
- storage.insertVerification(makeVerification({ receiptId: 'old-v', verifiedAt: old, disputed: true }));
228
- storage.insertVerification(makeVerification({ receiptId: 'new-v', verifiedAt: recent, disputed: true }));
229
- storage.upsertSession(makeSession({ sessionId: 'old-s', startedAt: old }));
230
- storage.upsertSession(makeSession({ sessionId: 'new-s', startedAt: recent }));
231
-
232
- const cutoff = now - 50000;
233
- const result = storage.pruneOlderThan(cutoff);
234
-
235
- expect(result.eventsDeleted).toBe(1);
236
- expect(result.receiptsDeleted).toBe(1);
237
- expect(result.verificationsDeleted).toBe(1);
238
- expect(result.sessionsDeleted).toBe(1);
239
-
240
- // Recent data should still exist
241
- expect(storage.getEventsByTimeRange(recent - 10, recent + 10)).toHaveLength(1);
242
- expect(storage.getReceiptsByTimeRange(recent - 10, recent + 10)).toHaveLength(1);
243
- });
244
- });