stellarium-ts 0.14.0 → 0.16.0

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.
@@ -1,4 +1,5 @@
1
1
  import { isFreshExpiry, safeVerifyData } from "../crypto/utils.js";
2
+ import { isInferenceManifest } from "./inference.js";
2
3
  /**
3
4
  * The object a node signs to broadcast its resources. Key order is part of the signed bytes.
4
5
  * Functions' `exec` implementations are dropped by JSON serialization on both ends.
@@ -114,13 +115,23 @@ export class ResourceRepository {
114
115
  async insertFunctions(resourceId, functions) {
115
116
  const createdAt = new Date().toISOString();
116
117
  for (const fn of functions) {
117
- const functionResult = await this.db.run('INSERT INTO resource_function (resource_id, name, return_type, description, restricted, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)', [resourceId, fn.name, fn.returnType, optionalText(fn.description), fn.restricted === true ? 1 : 0, createdAt, createdAt]);
118
+ const functionResult = await this.db.run('INSERT INTO resource_function (resource_id, name, return_type, description, restricted, kind, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [resourceId, fn.name, fn.returnType, optionalText(fn.description), fn.restricted === true ? 1 : 0, fn.kind === 'inference' ? 'inference' : 'fixed', createdAt, createdAt]);
118
119
  const functionId = functionResult.lastID;
119
120
  for (const parameter of fn.parameters) {
120
121
  await this.db.run('INSERT INTO resource_function_parameter (resource_function_id, name, parameter_type, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)', [functionId, parameter.name, parameter.type, optionalText(parameter.description), createdAt, createdAt]);
121
122
  }
122
- if (fn.paymentManifest) {
123
- await this.db.run('INSERT INTO resource_function_payment_manifest (resource_function_id, cost_lamports, recipient_address, created_at, updated_at) VALUES (?, ?, ?, ?, ?)', [functionId, fn.paymentManifest.costLamports, fn.paymentManifest.recipientAddress, createdAt, createdAt]);
123
+ const manifest = fn.paymentManifest;
124
+ if (manifest) {
125
+ const inference = isInferenceManifest(manifest);
126
+ await this.db.run('INSERT INTO resource_function_payment_manifest (resource_function_id, cost_micro_usdc, input_cost_micro_usdc_per_million_tokens, output_cost_micro_usdc_per_million_tokens, recipient_address, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)', [
127
+ functionId,
128
+ inference ? null : manifest.costMicroUsdc,
129
+ inference ? manifest.inputCostMicroUsdcPerMillionTokens : null,
130
+ inference ? manifest.outputCostMicroUsdcPerMillionTokens : null,
131
+ manifest.recipientAddress,
132
+ createdAt,
133
+ createdAt,
134
+ ]);
124
135
  }
125
136
  }
126
137
  }
@@ -191,6 +202,7 @@ export class ResourceRepository {
191
202
  exec: async () => undefined,
192
203
  paymentManifest: await this.getResourceFunctionPaymentManifest(fn.id),
193
204
  restricted: fn.restricted === 1 || undefined,
205
+ kind: fn.kind === 'inference' ? 'inference' : undefined,
194
206
  };
195
207
  }
196
208
  async getResourceFunctions(resourceId) {
@@ -204,6 +216,7 @@ export class ResourceRepository {
204
216
  exec: async () => undefined,
205
217
  paymentManifest: await this.getResourceFunctionPaymentManifest(f.id),
206
218
  restricted: f.restricted === 1 || undefined,
219
+ kind: f.kind === 'inference' ? 'inference' : undefined,
207
220
  })));
208
221
  }
209
222
  async getResourceParameters(resourceFunctionId) {
@@ -218,9 +231,17 @@ export class ResourceRepository {
218
231
  if (!result) {
219
232
  return undefined;
220
233
  }
234
+ if (result.cost_micro_usdc === null) {
235
+ return {
236
+ id: result.id,
237
+ inputCostMicroUsdcPerMillionTokens: result.input_cost_micro_usdc_per_million_tokens,
238
+ outputCostMicroUsdcPerMillionTokens: result.output_cost_micro_usdc_per_million_tokens,
239
+ recipientAddress: result.recipient_address,
240
+ };
241
+ }
221
242
  return {
222
243
  id: result.id,
223
- costLamports: result.cost_lamports,
244
+ costMicroUsdc: result.cost_micro_usdc,
224
245
  recipientAddress: result.recipient_address,
225
246
  };
226
247
  }
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAgC,MAAM,SAAS,CAAC;AAKhF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAK5C,MAAM,WAAW,eAAe;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,oDAAoD;IACpD,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;CAC/B;AAED,UAAU,YAAY;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,eAAe,CAAC;CAC/B;AAiDD,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,aAAa,MAAM,CAAC;AAsHjC;;;;GAIG;AACH,QAAA,MAAM,MAAM,EAAE,kBAAkB,CAAC,YAAY,CAqS5C,CAAC;eAGa,MAAM"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAgC,MAAM,SAAS,CAAC;AAMhF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAK5C,MAAM,WAAW,eAAe;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,oDAAoD;IACpD,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;CAC/B;AAED,UAAU,YAAY;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,eAAe,CAAC;CAC/B;AAoDD,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,aAAa,MAAM,CAAC;AA4LjC;;;;GAIG;AACH,QAAA,MAAM,MAAM,EAAE,kBAAkB,CAAC,YAAY,CAuX5C,CAAC;eAGa,MAAM"}
package/dist/routes.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { createHash, timingSafeEqual } from 'node:crypto';
2
2
  import rateLimit from '@fastify/rate-limit';
3
3
  import { interpretParsed, parseScript } from 'requestscript';
4
- import { invocationHash, paymentVoucherPayload } from './resource/invoker.js';
4
+ import { inferenceVoucherPayload, invocationHash, isInferenceVoucher, paymentVoucherPayload } from './resource/invoker.js';
5
+ import { inferenceCharge, isInferenceFunction, isInferenceManifest, parseMaxTokens } from './resource/inference.js';
5
6
  import { safeVerifyData } from './crypto/utils.js';
6
7
  import { InvalidPublicKeyError } from './grant/service.js';
7
8
  // Body schemas check only the shape the handlers rely on. Signed payloads (resources) are
@@ -48,35 +49,63 @@ const runRequestSchema = {
48
49
  required: ['source'],
49
50
  properties: { source: { type: 'string' } },
50
51
  };
52
+ const resourceCallProperties = {
53
+ resourcePath: { type: 'string' },
54
+ resourceName: { type: 'string' },
55
+ functionName: { type: 'string' },
56
+ parameters: {
57
+ type: 'array',
58
+ items: { type: 'object', required: ['name'], properties: { name: { type: 'string' } } },
59
+ },
60
+ // Shape only: the grants inside are signed, so coercing them would change the signed bytes.
61
+ authorization: { type: 'object' },
62
+ };
63
+ const fixedVoucherSchema = {
64
+ type: 'object',
65
+ required: ['requestId', 'invocationId', 'amount', 'recipientAddress', 'signature', 'expiresAt', 'invocationHash', 'publicKey'],
66
+ properties: {
67
+ requestId: { type: 'string' },
68
+ invocationId: { type: 'string' },
69
+ amount: { type: 'integer' },
70
+ recipientAddress: { type: 'string' },
71
+ signature: { type: 'string' },
72
+ expiresAt: { type: 'string' },
73
+ invocationHash: { type: 'string' },
74
+ publicKey: { type: 'string' },
75
+ },
76
+ };
77
+ const inferenceVoucherSchema = {
78
+ type: 'object',
79
+ required: ['kind', 'requestId', 'invocationId', 'maxAmount', 'inputCostMicroUsdcPerMillionTokens', 'outputCostMicroUsdcPerMillionTokens', 'maxTokens', 'recipientAddress', 'signature', 'expiresAt', 'invocationHash', 'publicKey'],
80
+ properties: {
81
+ kind: { const: 'inference' },
82
+ requestId: { type: 'string' },
83
+ invocationId: { type: 'string' },
84
+ maxAmount: { type: 'integer', minimum: 0 },
85
+ inputCostMicroUsdcPerMillionTokens: { type: 'integer', minimum: 0 },
86
+ outputCostMicroUsdcPerMillionTokens: { type: 'integer', minimum: 0 },
87
+ maxTokens: { type: 'integer', minimum: 1 },
88
+ recipientAddress: { type: 'string' },
89
+ signature: { type: 'string' },
90
+ expiresAt: { type: 'string' },
91
+ invocationHash: { type: 'string' },
92
+ publicKey: { type: 'string' },
93
+ },
94
+ };
51
95
  const resourceRunRequestSchema = {
52
96
  type: 'object',
53
97
  required: ['resourcePath', 'resourceName', 'functionName', 'parameters'],
54
98
  properties: {
55
- resourcePath: { type: 'string' },
56
- resourceName: { type: 'string' },
57
- functionName: { type: 'string' },
58
- parameters: {
59
- type: 'array',
60
- items: { type: 'object', required: ['name'], properties: { name: { type: 'string' } } },
61
- },
62
- paymentVoucher: {
63
- type: 'object',
64
- required: ['requestId', 'invocationId', 'amount', 'recipientAddress', 'signature', 'expiresAt', 'invocationHash', 'publicKey'],
65
- properties: {
66
- requestId: { type: 'string' },
67
- invocationId: { type: 'string' },
68
- amount: { type: 'integer' },
69
- recipientAddress: { type: 'string' },
70
- signature: { type: 'string' },
71
- expiresAt: { type: 'string' },
72
- invocationHash: { type: 'string' },
73
- publicKey: { type: 'string' },
74
- },
75
- },
76
- // Shape only: the grants inside are signed, so coercing them would change the signed bytes.
77
- authorization: { type: 'object' },
99
+ ...resourceCallProperties,
100
+ // An inference voucher has `kind` and no `amount`, so at most one shape matches.
101
+ paymentVoucher: { oneOf: [fixedVoucherSchema, inferenceVoucherSchema] },
78
102
  },
79
103
  };
104
+ const resourceEstimateRequestSchema = {
105
+ type: 'object',
106
+ required: ['resourcePath', 'resourceName', 'functionName', 'parameters'],
107
+ properties: resourceCallProperties,
108
+ };
80
109
  const grantRenewalRequestSchema = {
81
110
  type: 'object',
82
111
  required: ['grantId', 'publicKey', 'nonce', 'expiresAt', 'signature'],
@@ -102,16 +131,15 @@ function secretsEqual(a, b) {
102
131
  }
103
132
  /**
104
133
  * A voucher is valid when it is bound to this exact call, signed by its public key, unexpired,
105
- * and pays at least the function's cost to the function's recipient.
134
+ * and pays the function's recipient. A fixed voucher must pay at least the function's cost. An inference
135
+ * voucher must be for an inference function, carry its rates, and sign the call's `maxTokens`.
136
+ * Whether its `maxAmount` covers the call is checked separately, against the function's estimator.
106
137
  */
107
- function isValidPaymentVoucher(voucher, manifest, request) {
138
+ function isValidPaymentVoucher(voucher, resourceFunction, manifest, request) {
108
139
  const expectedHash = invocationHash(request.resourcePath, request.resourceName, request.functionName, request.parameters);
109
140
  if (voucher.invocationHash !== expectedHash) {
110
141
  return false;
111
142
  }
112
- if (voucher.amount < manifest.costLamports) {
113
- return false;
114
- }
115
143
  if (voucher.recipientAddress !== manifest.recipientAddress) {
116
144
  return false;
117
145
  }
@@ -119,8 +147,47 @@ function isValidPaymentVoucher(voucher, manifest, request) {
119
147
  if (!Number.isFinite(expiresAt) || expiresAt <= Date.now()) {
120
148
  return false;
121
149
  }
150
+ if (isInferenceVoucher(voucher)) {
151
+ if (!isInferenceFunction(resourceFunction) || !isInferenceManifest(manifest)) {
152
+ return false;
153
+ }
154
+ if (voucher.inputCostMicroUsdcPerMillionTokens !== manifest.inputCostMicroUsdcPerMillionTokens
155
+ || voucher.outputCostMicroUsdcPerMillionTokens !== manifest.outputCostMicroUsdcPerMillionTokens) {
156
+ return false;
157
+ }
158
+ if (voucher.maxTokens !== parseMaxTokens(request.parameters)) {
159
+ return false;
160
+ }
161
+ return safeVerifyData(inferenceVoucherPayload(voucher), voucher.publicKey, voucher.signature);
162
+ }
163
+ if (isInferenceFunction(resourceFunction) || isInferenceManifest(manifest)) {
164
+ return false;
165
+ }
166
+ if (voucher.amount < manifest.costMicroUsdc) {
167
+ return false;
168
+ }
122
169
  return safeVerifyData(paymentVoucherPayload(voucher), voucher.publicKey, voucher.signature);
123
170
  }
171
+ /** The function's own input-token estimate, or undefined if its estimator failed or returned something other than a count. */
172
+ async function estimateInputTokens(resourceFunction, request) {
173
+ try {
174
+ const inputTokens = await resourceFunction.estimateInputTokens?.(request.parameters);
175
+ return Number.isSafeInteger(inputTokens) && inputTokens >= 0 ? inputTokens : undefined;
176
+ }
177
+ catch {
178
+ return undefined;
179
+ }
180
+ }
181
+ /** What the operator will accrue for `usage`: its charge at the voucher's rates, capped at `maxAmount`. */
182
+ function cappedInferenceCharge(voucher, usage) {
183
+ try {
184
+ return Math.min(inferenceCharge(voucher, usage.inputTokens, usage.outputTokens), voucher.maxAmount);
185
+ }
186
+ catch {
187
+ // The charge isn't a safe integer, so it is over any maxAmount.
188
+ return voucher.maxAmount;
189
+ }
190
+ }
124
191
  /**
125
192
  * Encapsulates the routes
126
193
  * @param fastify Encapsulated Fastify Instance
@@ -187,11 +254,16 @@ const routes = async (fastify, options) => {
187
254
  type: parameter.type,
188
255
  description: parameter.description,
189
256
  })),
190
- paymentManifest: fn.paymentManifest ? ({
191
- costLamports: fn.paymentManifest.costLamports,
257
+ paymentManifest: !fn.paymentManifest ? undefined : isInferenceManifest(fn.paymentManifest) ? {
258
+ inputCostMicroUsdcPerMillionTokens: fn.paymentManifest.inputCostMicroUsdcPerMillionTokens,
259
+ outputCostMicroUsdcPerMillionTokens: fn.paymentManifest.outputCostMicroUsdcPerMillionTokens,
192
260
  recipientAddress: fn.paymentManifest.recipientAddress,
193
- }) : undefined,
261
+ } : {
262
+ costMicroUsdc: fn.paymentManifest.costMicroUsdc,
263
+ recipientAddress: fn.paymentManifest.recipientAddress,
264
+ },
194
265
  restricted: fn.restricted || undefined,
266
+ kind: isInferenceFunction(fn) ? 'inference' : undefined,
195
267
  })),
196
268
  baseUrl: resource.baseUrl,
197
269
  })),
@@ -240,19 +312,20 @@ const routes = async (fastify, options) => {
240
312
  reply.status(500).send({ error: 'Internal server error' });
241
313
  }
242
314
  });
243
- fastify.post('/run/resource', {
244
- schema: { body: resourceRunRequestSchema },
245
- config: { rateLimit: options.rateLimit ?? { max: 100, timeWindow: '1 minute' } },
246
- }, async (request, reply) => {
247
- const resourceRequest = request.body;
248
- const paymentVoucher = resourceRequest.paymentVoucher;
315
+ /**
316
+ * Finds the local function a call names and checks its parameters and, for a restricted function,
317
+ * its authorization. Sends the refusal and returns undefined if a check fails.
318
+ */
319
+ async function checkCall(resourceRequest, reply) {
249
320
  const resource = await options.service.resourceRepository.getInternalResource(resourceRequest.resourcePath, resourceRequest.resourceName);
250
321
  if (!resource) {
251
- return reply.status(400).send({ error: 'Resource not found' });
322
+ reply.status(400).send({ error: 'Resource not found' });
323
+ return undefined;
252
324
  }
253
325
  const resourceFunction = resource.functions.find(fn => fn.name === resourceRequest.functionName);
254
326
  if (!resourceFunction) {
255
- return reply.status(400).send({ error: 'Function not found' });
327
+ reply.status(400).send({ error: 'Function not found' });
328
+ return undefined;
256
329
  }
257
330
  // Grant constraints and the function both read parameters by name, so each name must be
258
331
  // declared and appear once. Otherwise a duplicate or extra parameter could pass a
@@ -260,7 +333,8 @@ const routes = async (fastify, options) => {
260
333
  const declared = new Set(resourceFunction.parameters.map(parameter => parameter.name));
261
334
  const names = resourceRequest.parameters.map(parameter => parameter.name);
262
335
  if (new Set(names).size !== names.length || !names.every(name => declared.has(name))) {
263
- return reply.status(400).send({ error: 'Parameters must be declared by the function and appear once each' });
336
+ reply.status(400).send({ error: 'Parameters must be declared by the function and appear once each' });
337
+ return undefined;
264
338
  }
265
339
  // A restricted function is never run without a grant from this node. Checked before the
266
340
  // voucher, so an unauthorized caller never gets near settlement.
@@ -268,33 +342,104 @@ const routes = async (fastify, options) => {
268
342
  // Tells a caller without a usable grant where it can ask for one.
269
343
  const grantEndpoint = options.service.grantService.acceptsRequests ? `${options.service.config.baseUrl}/v1/grants/request` : undefined;
270
344
  if (!resourceRequest.authorization) {
271
- return reply.status(403).send({ error: 'Authorization is required', grantEndpoint });
345
+ reply.status(403).send({ error: 'Authorization is required', grantEndpoint });
346
+ return undefined;
272
347
  }
273
348
  if (!await options.service.grantService.verifyInvocation(resourceRequest.authorization, resourceRequest)) {
274
- return reply.status(403).send({ error: 'Invalid authorization', grantEndpoint });
349
+ reply.status(403).send({ error: 'Invalid authorization', grantEndpoint });
350
+ return undefined;
275
351
  }
276
352
  }
353
+ return { resource, resourceFunction };
354
+ }
355
+ /**
356
+ * Prices a call to a paid inference function before the caller signs its voucher: the function's
357
+ * estimate of the input tokens, and `maxAmount`, the charge for those plus `maxTokens` output tokens.
358
+ * Runs the provider's estimator, so a restricted function needs authorization here too.
359
+ */
360
+ fastify.post('/run/resource/estimate', {
361
+ schema: { body: resourceEstimateRequestSchema },
362
+ config: { rateLimit: options.rateLimit ?? { max: 100, timeWindow: '1 minute' } },
363
+ }, async (request, reply) => {
364
+ const resourceRequest = request.body;
365
+ const call = await checkCall(resourceRequest, reply);
366
+ if (!call) {
367
+ return reply;
368
+ }
369
+ const { resourceFunction } = call;
370
+ const manifest = resourceFunction.paymentManifest;
371
+ if (!isInferenceFunction(resourceFunction) || !isInferenceManifest(manifest)) {
372
+ return reply.status(400).send({ error: 'Function is not a paid inference function' });
373
+ }
374
+ const maxTokens = parseMaxTokens(resourceRequest.parameters);
375
+ if (maxTokens === undefined) {
376
+ return reply.status(400).send({ error: 'maxTokens must be a positive int32' });
377
+ }
378
+ const inputTokens = await estimateInputTokens(resourceFunction, resourceRequest);
379
+ if (inputTokens === undefined) {
380
+ return reply.status(500).send({ error: 'Failed to estimate input tokens' });
381
+ }
382
+ return { inputTokens, maxAmount: inferenceCharge(manifest, inputTokens, maxTokens) };
383
+ });
384
+ fastify.post('/run/resource', {
385
+ schema: { body: resourceRunRequestSchema },
386
+ config: { rateLimit: options.rateLimit ?? { max: 100, timeWindow: '1 minute' } },
387
+ }, async (request, reply) => {
388
+ const resourceRequest = request.body;
389
+ const paymentVoucher = resourceRequest.paymentVoucher;
390
+ const call = await checkCall(resourceRequest, reply);
391
+ if (!call) {
392
+ return reply;
393
+ }
394
+ const { resource, resourceFunction } = call;
277
395
  // Payment manifests are per function. A paid function is never run without a valid voucher.
278
396
  const paymentManifest = resourceFunction.paymentManifest;
279
397
  if (paymentManifest) {
280
398
  if (!paymentVoucher) {
281
399
  return reply.status(402).send({ error: 'Payment voucher is required' });
282
400
  }
283
- if (!isValidPaymentVoucher(paymentVoucher, paymentManifest, resourceRequest)) {
401
+ if (!isValidPaymentVoucher(paymentVoucher, resourceFunction, paymentManifest, resourceRequest)) {
284
402
  return reply.status(400).send({ error: 'Invalid payment voucher' });
285
403
  }
286
404
  if (await options.service.grantService.isBlocked(paymentVoucher.publicKey)) {
287
405
  return reply.status(403).send({ error: 'Caller is blocked' });
288
406
  }
407
+ // Re-estimated here, so inference never runs on a voucher that can't cover the expected cost.
408
+ if (isInferenceVoucher(paymentVoucher)) {
409
+ const inputTokens = await estimateInputTokens(resourceFunction, resourceRequest);
410
+ if (inputTokens === undefined) {
411
+ return reply.status(500).send({ error: 'Failed to estimate input tokens' });
412
+ }
413
+ const requiredMaxAmount = inferenceCharge(paymentVoucher, inputTokens, paymentVoucher.maxTokens);
414
+ if (paymentVoucher.maxAmount < requiredMaxAmount) {
415
+ return reply.status(402).send({ error: `Payment voucher maxAmount ${paymentVoucher.maxAmount} is below the estimated ${requiredMaxAmount} micro-USDC`, requiredMaxAmount });
416
+ }
417
+ }
289
418
  }
290
419
  const result = await options.service.resourceInvoker.invoke(resource, resourceRequest.functionName, resourceRequest.parameters);
420
+ let settlement;
421
+ if (paymentManifest && paymentVoucher && !result.error) {
422
+ if (!isInferenceVoucher(paymentVoucher)) {
423
+ settlement = { voucher: paymentVoucher };
424
+ }
425
+ else if (result.usage) {
426
+ // The function may report more output than it was allowed; the caller only signed for maxTokens.
427
+ const usage = { inputTokens: result.usage.inputTokens, outputTokens: Math.min(result.usage.outputTokens, paymentVoucher.maxTokens) };
428
+ result.usage = usage;
429
+ // A free call has nothing to settle, and the operator refuses an empty receipt.
430
+ if (cappedInferenceCharge(paymentVoucher, usage) > 0) {
431
+ settlement = { voucher: paymentVoucher, usage };
432
+ }
433
+ }
434
+ }
291
435
  // Settle only verified vouchers, and only after a successful invoke. A voucher sent to a free function is ignored.
292
436
  // Run this on a separate thread to avoid blocking the main thread.
293
- if (paymentManifest && paymentVoucher && !result.error) {
437
+ if (settlement) {
438
+ const payment = settlement;
294
439
  setTimeout(async () => {
295
440
  // Nothing awaits this callback, so an uncaught error here would be an unhandled rejection.
296
441
  try {
297
- await options.service.operatorClient.settlePayment({ voucher: paymentVoucher });
442
+ await options.service.operatorClient.settlePayment(payment);
298
443
  }
299
444
  catch (error) {
300
445
  fastify.log.error(error, 'Failed to settle payment');
package/dist/server.d.ts CHANGED
@@ -6,7 +6,7 @@ import type { ApproveGrant, GrantOptions } from './grant/service.js';
6
6
  import { StellariumResource } from './resource/repository.js';
7
7
  import { RateLimitConfig } from './routes.js';
8
8
  import { type NodeConfig } from './service.js';
9
- export declare const DEFAULT_COST_LIMIT_LAMPORTS = 10000;
9
+ export declare const DEFAULT_COST_LIMIT_MICRO_USDC = 10000;
10
10
  export declare const DEFAULT_GRANT_REFRESH_INTERVAL_MS: number;
11
11
  export interface StellariumNodeOptions {
12
12
  port?: number;
@@ -24,8 +24,13 @@ export interface StellariumNodeOptions {
24
24
  apiKey?: string;
25
25
  /** Defaults to `OPERATOR_URL`. */
26
26
  operatorUrl?: string;
27
- /** Most this node pays for one remote call. Defaults to `COST_LIMIT_LAMPORTS`, then 10,000. */
28
- costLimitLamports?: number;
27
+ /** Most this node pays for one remote call. Defaults to `COST_LIMIT_MICRO_USDC`, then 10,000 ($0.01). */
28
+ costLimitMicroUsdc?: number;
29
+ /**
30
+ * Most this node signs for in one remote inference call (the voucher's `maxAmount`).
31
+ * Defaults to `INFERENCE_COST_LIMIT_MICRO_USDC`, then to the resolved `costLimitMicroUsdc`.
32
+ */
33
+ inferenceCostLimitMicroUsdc?: number;
29
34
  /** SQLite file. Defaults to `./requestscript.db`; use ':memory:' for tests. */
30
35
  databasePath?: string;
31
36
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/dist/server.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import 'dotenv/config';
2
2
  import { DEFAULT_DATABASE_PATH, openDatabase } from './db/db.js';
3
+ import { validateInferenceFunction } from './resource/inference.js';
3
4
  import routes from './routes.js';
4
5
  import { createService } from './service.js';
5
6
  import { StartupService } from './startup.js';
6
- export const DEFAULT_COST_LIMIT_LAMPORTS = 10_000;
7
+ export const DEFAULT_COST_LIMIT_MICRO_USDC = 10_000;
7
8
  export const DEFAULT_GRANT_REFRESH_INTERVAL_MS = 1000 * 60 * 5;
8
9
  function required(name, value) {
9
10
  if (!value) {
@@ -15,9 +16,16 @@ function hostsPaidFunctions(resources) {
15
16
  return resources.some(resource => resource.functions.some(fn => fn.paymentManifest));
16
17
  }
17
18
  export function resolveConfig(options, resources = []) {
18
- const costLimit = options.costLimitLamports ?? (process.env.COST_LIMIT_LAMPORTS ? Number(process.env.COST_LIMIT_LAMPORTS) : DEFAULT_COST_LIMIT_LAMPORTS);
19
+ const costLimit = options.costLimitMicroUsdc ?? (process.env.COST_LIMIT_MICRO_USDC ? Number(process.env.COST_LIMIT_MICRO_USDC) : DEFAULT_COST_LIMIT_MICRO_USDC);
19
20
  if (!Number.isSafeInteger(costLimit) || costLimit < 0) {
20
- throw new Error('costLimitLamports must be a nonnegative integer');
21
+ throw new Error('costLimitMicroUsdc must be a nonnegative integer');
22
+ }
23
+ const inferenceCostLimit = options.inferenceCostLimitMicroUsdc ?? (process.env.INFERENCE_COST_LIMIT_MICRO_USDC ? Number(process.env.INFERENCE_COST_LIMIT_MICRO_USDC) : costLimit);
24
+ if (!Number.isSafeInteger(inferenceCostLimit) || inferenceCostLimit < 0) {
25
+ throw new Error('inferenceCostLimitMicroUsdc must be a nonnegative integer');
26
+ }
27
+ for (const resource of resources) {
28
+ resource.functions.forEach(validateInferenceFunction);
21
29
  }
22
30
  return {
23
31
  baseUrl: required('BASE_URL', options.baseUrl ?? process.env.BASE_URL),
@@ -28,7 +36,8 @@ export function resolveConfig(options, resources = []) {
28
36
  operatorUrl: hostsPaidFunctions(resources)
29
37
  ? required('OPERATOR_URL', options.operatorUrl ?? process.env.OPERATOR_URL)
30
38
  : options.operatorUrl ?? process.env.OPERATOR_URL,
31
- costLimitLamports: costLimit,
39
+ costLimitMicroUsdc: costLimit,
40
+ inferenceCostLimitMicroUsdc: inferenceCostLimit,
32
41
  };
33
42
  }
34
43
  export class StellariumNode {
package/dist/service.d.ts CHANGED
@@ -17,7 +17,9 @@ export interface NodeConfig {
17
17
  /** Operator base URL, including `/v1`. Required when the node hosts paid functions. */
18
18
  operatorUrl?: string;
19
19
  /** Most this node will pay for one remote call. */
20
- costLimitLamports: number;
20
+ costLimitMicroUsdc: number;
21
+ /** Most this node will sign for in one remote inference call. */
22
+ inferenceCostLimitMicroUsdc: number;
21
23
  }
22
24
  export interface Service {
23
25
  config: NodeConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEvF,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,OAAO,EAAE,KAAK,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAErE,4GAA4G;AAC5G,MAAM,WAAW,UAAU;IACvB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,iBAAiB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,OAAO;IACpB,MAAM,EAAE,UAAU,CAAC;IACnB,cAAc,EAAE,cAAc,CAAC;IAC/B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,eAAe,EAAE,eAAe,CAAC;IACjC,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,YAAY,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC3B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,YAAY,CAAC;CAC/B;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAoBtI"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEvF,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,OAAO,EAAE,KAAK,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAErE,4GAA4G;AAC5G,MAAM,WAAW,UAAU;IACvB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iEAAiE;IACjE,2BAA2B,EAAE,MAAM,CAAC;CACvC;AAED,MAAM,WAAW,OAAO;IACpB,MAAM,EAAE,UAAU,CAAC;IACnB,cAAc,EAAE,cAAc,CAAC;IAC/B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,eAAe,EAAE,eAAe,CAAC;IACjC,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,YAAY,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC3B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,YAAY,CAAC;CAC/B;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAoBtI"}
package/dist/service.js CHANGED
@@ -9,7 +9,7 @@ export function createService(db, resources, config, options = {}) {
9
9
  const resourceRepository = new ResourceRepository(db, resources, config.baseUrl);
10
10
  const resourceResolver = new CombinationResourceResolver(resourceRepository);
11
11
  const grantService = new GrantService(new GrantRepository(db), config, options.approveGrant);
12
- const resourceInvoker = new CombinationResourceInvoker(resourceRepository, config.costLimitLamports, config.publicKey, config.privateKey, grantService);
12
+ const resourceInvoker = new CombinationResourceInvoker(resourceRepository, config.costLimitMicroUsdc, config.publicKey, config.privateKey, grantService, config.inferenceCostLimitMicroUsdc);
13
13
  const operatorClient = new OperatorClient(config.operatorUrl);
14
14
  return {
15
15
  config,
@@ -17,7 +17,7 @@ describe("openDatabase", () => {
17
17
  const filename = path.join(dir, "node.db");
18
18
  await (await openDatabase(filename)).close();
19
19
  const db = await openDatabase(filename);
20
- expect(await db.get("PRAGMA user_version")).toEqual({ user_version: 3 });
20
+ expect(await db.get("PRAGMA user_version")).toEqual({ user_version: 5 });
21
21
  await db.close();
22
22
  });
23
23
  test("rebuilds resource tables from the old schema and keeps peers", async () => {
@@ -61,4 +61,21 @@ describe("openDatabase", () => {
61
61
  expect(await reopened.all("SELECT id FROM held_grant")).toEqual([{ id: "g" }]);
62
62
  await reopened.close();
63
63
  });
64
+ test("rebuilds a version 4 manifest table so inference functions can omit cost_micro_usdc", async () => {
65
+ const filename = path.join(dir, "v4.db");
66
+ const old = await open({ filename, driver: sqlite3.Database });
67
+ await old.exec(`
68
+ CREATE TABLE resource_function_payment_manifest (id INTEGER PRIMARY KEY AUTOINCREMENT, resource_function_id INTEGER NOT NULL,
69
+ cost_micro_usdc INTEGER NOT NULL, recipient_address TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
70
+ UNIQUE (resource_function_id));
71
+ PRAGMA user_version = 4;
72
+ `);
73
+ await old.close();
74
+ const db = await openDatabase(filename);
75
+ const functionColumns = await db.all("PRAGMA table_info(resource_function)");
76
+ expect(functionColumns.map(column => column.name)).toContain("kind");
77
+ await db.run(`INSERT INTO resource_function_payment_manifest (resource_function_id, input_cost_micro_usdc_per_million_tokens,
78
+ output_cost_micro_usdc_per_million_tokens, recipient_address, created_at, updated_at) VALUES (1, 3, 15, 'r', 'now', 'now')`);
79
+ await db.close();
80
+ });
64
81
  });
@@ -9,5 +9,14 @@ export declare const COST = 10000;
9
9
  export declare function testConfig(overrides?: Partial<NodeConfig>): NodeConfig;
10
10
  /** The fixture provider's resource: paid `temperature` and free `humidity`. */
11
11
  export declare function weatherResource(baseUrl?: string): StellariumResource;
12
+ export declare const INFERENCE_RATES: {
13
+ inputCostMicroUsdcPerMillionTokens: number;
14
+ outputCostMicroUsdcPerMillionTokens: number;
15
+ };
16
+ /**
17
+ * A fixture provider's model: paid inference `complete`, which counts one token per four prompt characters
18
+ * and answers with `maxTokens` tokens, a restricted paid copy `completePrivate`, and free inference `draft`.
19
+ */
20
+ export declare function modelResource(baseUrl?: string): StellariumResource;
12
21
  export declare const temperatureSource = "request GetTemperature {\n const weather: com.example.Weather\n\n return weather.temperature(city: \"Oslo\")\n}";
13
22
  //# sourceMappingURL=helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/tests/helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,wBAAgB,qBAAqB;IAM7B,SAAS;IACT,UAAU;EAEjB;AAED,eAAO,MAAM,SAAS,iDAAiD,CAAC;AACxE,eAAO,MAAM,IAAI,QAAQ,CAAC;AAE1B,wBAAgB,UAAU,CAAC,SAAS,GAAE,OAAO,CAAC,UAAU,CAAM,GAAG,UAAU,CAS1E;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,OAAO,SAA0B,GAAG,kBAAkB,CAuBrF;AAED,eAAO,MAAM,iBAAiB,sHAAoH,CAAC"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/tests/helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,wBAAgB,qBAAqB;IAM7B,SAAS;IACT,UAAU;EAEjB;AAED,eAAO,MAAM,SAAS,iDAAiD,CAAC;AACxE,eAAO,MAAM,IAAI,QAAQ,CAAC;AAE1B,wBAAgB,UAAU,CAAC,SAAS,GAAE,OAAO,CAAC,UAAU,CAAM,GAAG,UAAU,CAU1E;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,OAAO,SAA0B,GAAG,kBAAkB,CAuBrF;AAED,eAAO,MAAM,eAAe;IAAK,kCAAkC;IAAa,mCAAmC;CAAc,CAAC;AAElI;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,SAA0B,GAAG,kBAAkB,CA0BnF;AAED,eAAO,MAAM,iBAAiB,sHAAoH,CAAC"}
@@ -17,7 +17,8 @@ export function testConfig(overrides = {}) {
17
17
  ...generateBase64KeyPair(),
18
18
  apiKey: "secret",
19
19
  operatorUrl: "http://operator.invalid/v1",
20
- costLimitLamports: 100_000,
20
+ costLimitMicroUsdc: 100_000,
21
+ inferenceCostLimitMicroUsdc: 100_000,
21
22
  ...overrides,
22
23
  };
23
24
  }
@@ -35,7 +36,7 @@ export function weatherResource(baseUrl = "http://127.0.0.1:3002") {
35
36
  parameters: [{ name: "city", type: "string" }],
36
37
  returnType: "int32",
37
38
  exec: async (args) => (args.find((arg) => arg.name === "city")?.value === "Oslo" ? 12 : 20),
38
- paymentManifest: { costLamports: COST, recipientAddress: RECIPIENT },
39
+ paymentManifest: { costMicroUsdc: COST, recipientAddress: RECIPIENT },
39
40
  },
40
41
  {
41
42
  name: "humidity",
@@ -46,4 +47,36 @@ export function weatherResource(baseUrl = "http://127.0.0.1:3002") {
46
47
  ],
47
48
  };
48
49
  }
50
+ export const INFERENCE_RATES = { inputCostMicroUsdcPerMillionTokens: 3_000_000, outputCostMicroUsdcPerMillionTokens: 15_000_000 };
51
+ /**
52
+ * A fixture provider's model: paid inference `complete`, which counts one token per four prompt characters
53
+ * and answers with `maxTokens` tokens, a restricted paid copy `completePrivate`, and free inference `draft`.
54
+ */
55
+ export function modelResource(baseUrl = "http://127.0.0.1:3002") {
56
+ const prompt = (args) => String(args.find((arg) => arg.name === "prompt")?.value ?? "");
57
+ const complete = {
58
+ name: "complete",
59
+ kind: "inference",
60
+ parameters: [{ name: "prompt", type: "string" }, { name: "maxTokens", type: "int32" }],
61
+ returnType: "string",
62
+ estimateInputTokens: async (args) => Math.ceil(prompt(args).length / 4),
63
+ exec: async (args) => ({
64
+ value: `echo: ${prompt(args)}`,
65
+ usage: { inputTokens: Math.ceil(prompt(args).length / 4), outputTokens: Number(args.find((arg) => arg.name === "maxTokens")?.value) },
66
+ }),
67
+ paymentManifest: { ...INFERENCE_RATES, recipientAddress: RECIPIENT },
68
+ };
69
+ return {
70
+ id: 2,
71
+ baseUrl,
72
+ metadata: {},
73
+ path: "com.example",
74
+ name: "Model",
75
+ functions: [
76
+ complete,
77
+ { ...complete, name: "completePrivate", restricted: true },
78
+ { ...complete, name: "draft", paymentManifest: undefined, estimateInputTokens: undefined },
79
+ ],
80
+ };
81
+ }
49
82
  export const temperatureSource = 'request GetTemperature {\n const weather: com.example.Weather\n\n return weather.temperature(city: "Oslo")\n}';