@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,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
- });