stellarium-ts 0.16.0 → 0.18.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.
- package/dist/client/operatorAuth.d.ts +19 -0
- package/dist/client/operatorAuth.d.ts.map +1 -0
- package/dist/client/operatorAuth.js +21 -0
- package/dist/client/operatorClient.d.ts +103 -31
- package/dist/client/operatorClient.d.ts.map +1 -1
- package/dist/client/operatorClient.js +107 -23
- package/dist/client/runTokens.d.ts +23 -0
- package/dist/client/runTokens.d.ts.map +1 -0
- package/dist/client/runTokens.js +34 -0
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +8 -5
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/resource/inference.d.ts +5 -3
- package/dist/resource/inference.d.ts.map +1 -1
- package/dist/resource/inference.js +17 -7
- package/dist/resource/invoker.d.ts +48 -14
- package/dist/resource/invoker.d.ts.map +1 -1
- package/dist/resource/invoker.js +108 -38
- package/dist/resource/repository.d.ts +13 -7
- package/dist/resource/repository.d.ts.map +1 -1
- package/dist/resource/repository.js +23 -13
- package/dist/resource/settlement.d.ts +25 -0
- package/dist/resource/settlement.d.ts.map +1 -0
- package/dist/resource/settlement.js +59 -0
- package/dist/routes.d.ts +16 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +103 -76
- package/dist/server.d.ts +11 -6
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +32 -7
- package/dist/service.d.ts +18 -6
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +8 -3
- package/dist/tests/client/operatorClient.test.js +94 -36
- package/dist/tests/db/db.test.js +4 -4
- package/dist/tests/helpers.d.ts +4 -3
- package/dist/tests/helpers.d.ts.map +1 -1
- package/dist/tests/helpers.js +8 -6
- package/dist/tests/integration/network.test.js +69 -18
- package/dist/tests/resource/invoker.test.js +33 -9
- package/dist/tests/resource/repository.test.js +9 -9
- package/dist/tests/routes.test.js +165 -40
- package/dist/tests/server.test.js +15 -15
- package/package.json +3 -2
package/dist/routes.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FastifyPluginAsync } from 'fastify';
|
|
2
|
+
import { type RunTokenClaims } from './client/runTokens.js';
|
|
2
3
|
import type { Service } from './service.js';
|
|
3
4
|
export interface RateLimitConfig {
|
|
4
5
|
max: number;
|
|
@@ -9,6 +10,21 @@ interface RouteOptions {
|
|
|
9
10
|
service: Service;
|
|
10
11
|
rateLimit?: RateLimitConfig;
|
|
11
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Who is calling `POST /v1/run`: the holder of this node's API key, or an API client with a run token.
|
|
15
|
+
* Set by the route's preValidation hook.
|
|
16
|
+
*/
|
|
17
|
+
type RunCaller = {
|
|
18
|
+
kind: 'apiKey';
|
|
19
|
+
} | ({
|
|
20
|
+
kind: 'client';
|
|
21
|
+
token: string;
|
|
22
|
+
} & RunTokenClaims);
|
|
23
|
+
declare module 'fastify' {
|
|
24
|
+
interface FastifyRequest {
|
|
25
|
+
runCaller?: RunCaller;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
12
28
|
export declare const DEFAULT_PAGE_SIZE = 100;
|
|
13
29
|
export declare const MAX_PAGE_SIZE = 500;
|
|
14
30
|
/**
|
package/dist/routes.d.ts.map
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAgC,MAAM,SAAS,CAAC;AAEhF,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAM1E,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;AAoCD;;;GAGG;AACH,KAAK,SAAS,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG,CAAC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,cAAc,CAAC,CAAC;AAE3F,OAAO,QAAQ,SAAS,CAAC;IACrB,UAAU,cAAc;QACpB,SAAS,CAAC,EAAE,SAAS,CAAC;KACzB;CACJ;AAkBD,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,aAAa,MAAM,CAAC;AAwKjC;;;;GAIG;AACH,QAAA,MAAM,MAAM,EAAE,kBAAkB,CAAC,YAAY,CAma5C,CAAC;eAGa,MAAM"}
|
package/dist/routes.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
1
|
+
import { createHash, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
2
2
|
import rateLimit from '@fastify/rate-limit';
|
|
3
|
-
import { interpretParsed, parseScript } from 'requestscript';
|
|
3
|
+
import { interpretParsed, parseScript, RequestScriptError, TokenMeter } from 'requestscript';
|
|
4
|
+
import { looksLikeJwt } from './client/runTokens.js';
|
|
4
5
|
import { inferenceVoucherPayload, invocationHash, isInferenceVoucher, paymentVoucherPayload } from './resource/invoker.js';
|
|
5
|
-
import { inferenceCharge, isInferenceFunction, isInferenceManifest, parseMaxTokens } from './resource/inference.js';
|
|
6
|
+
import { estimateInputTokens, inferenceCharge, isInferenceFunction, isInferenceManifest, parseMaxTokens } from './resource/inference.js';
|
|
7
|
+
import { settlePaidCall } from './resource/settlement.js';
|
|
6
8
|
import { safeVerifyData } from './crypto/utils.js';
|
|
7
9
|
import { InvalidPublicKeyError } from './grant/service.js';
|
|
8
10
|
// Body schemas check only the shape the handlers rely on. Signed payloads (resources) are
|
|
@@ -62,12 +64,12 @@ const resourceCallProperties = {
|
|
|
62
64
|
};
|
|
63
65
|
const fixedVoucherSchema = {
|
|
64
66
|
type: 'object',
|
|
65
|
-
required: ['requestId', 'invocationId', 'amount', '
|
|
67
|
+
required: ['requestId', 'invocationId', 'amount', 'recipientOrganizationId', 'signature', 'expiresAt', 'invocationHash', 'publicKey'],
|
|
66
68
|
properties: {
|
|
67
69
|
requestId: { type: 'string' },
|
|
68
70
|
invocationId: { type: 'string' },
|
|
69
71
|
amount: { type: 'integer' },
|
|
70
|
-
|
|
72
|
+
recipientOrganizationId: { type: 'string' },
|
|
71
73
|
signature: { type: 'string' },
|
|
72
74
|
expiresAt: { type: 'string' },
|
|
73
75
|
invocationHash: { type: 'string' },
|
|
@@ -76,16 +78,16 @@ const fixedVoucherSchema = {
|
|
|
76
78
|
};
|
|
77
79
|
const inferenceVoucherSchema = {
|
|
78
80
|
type: 'object',
|
|
79
|
-
required: ['kind', 'requestId', 'invocationId', 'maxAmount', '
|
|
81
|
+
required: ['kind', 'requestId', 'invocationId', 'maxAmount', 'inputCostMicroUsdPerMillionTokens', 'outputCostMicroUsdPerMillionTokens', 'maxTokens', 'recipientOrganizationId', 'signature', 'expiresAt', 'invocationHash', 'publicKey'],
|
|
80
82
|
properties: {
|
|
81
83
|
kind: { const: 'inference' },
|
|
82
84
|
requestId: { type: 'string' },
|
|
83
85
|
invocationId: { type: 'string' },
|
|
84
86
|
maxAmount: { type: 'integer', minimum: 0 },
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
inputCostMicroUsdPerMillionTokens: { type: 'integer', minimum: 0 },
|
|
88
|
+
outputCostMicroUsdPerMillionTokens: { type: 'integer', minimum: 0 },
|
|
87
89
|
maxTokens: { type: 'integer', minimum: 1 },
|
|
88
|
-
|
|
90
|
+
recipientOrganizationId: { type: 'string' },
|
|
89
91
|
signature: { type: 'string' },
|
|
90
92
|
expiresAt: { type: 'string' },
|
|
91
93
|
invocationHash: { type: 'string' },
|
|
@@ -131,16 +133,16 @@ function secretsEqual(a, b) {
|
|
|
131
133
|
}
|
|
132
134
|
/**
|
|
133
135
|
* A voucher is valid when it is bound to this exact call, signed by its public key, unexpired,
|
|
134
|
-
* and pays
|
|
136
|
+
* and pays this node's organization. A fixed voucher must pay at least the function's cost. An inference
|
|
135
137
|
* voucher must be for an inference function, carry its rates, and sign the call's `maxTokens`.
|
|
136
138
|
* Whether its `maxAmount` covers the call is checked separately, against the function's estimator.
|
|
137
139
|
*/
|
|
138
|
-
function isValidPaymentVoucher(voucher, resourceFunction, manifest, request) {
|
|
140
|
+
function isValidPaymentVoucher(voucher, resourceFunction, manifest, request, organizationId) {
|
|
139
141
|
const expectedHash = invocationHash(request.resourcePath, request.resourceName, request.functionName, request.parameters);
|
|
140
142
|
if (voucher.invocationHash !== expectedHash) {
|
|
141
143
|
return false;
|
|
142
144
|
}
|
|
143
|
-
if (voucher.
|
|
145
|
+
if (!organizationId || voucher.recipientOrganizationId !== organizationId) {
|
|
144
146
|
return false;
|
|
145
147
|
}
|
|
146
148
|
const expiresAt = Date.parse(voucher.expiresAt);
|
|
@@ -151,8 +153,8 @@ function isValidPaymentVoucher(voucher, resourceFunction, manifest, request) {
|
|
|
151
153
|
if (!isInferenceFunction(resourceFunction) || !isInferenceManifest(manifest)) {
|
|
152
154
|
return false;
|
|
153
155
|
}
|
|
154
|
-
if (voucher.
|
|
155
|
-
|| voucher.
|
|
156
|
+
if (voucher.inputCostMicroUsdPerMillionTokens !== manifest.inputCostMicroUsdPerMillionTokens
|
|
157
|
+
|| voucher.outputCostMicroUsdPerMillionTokens !== manifest.outputCostMicroUsdPerMillionTokens) {
|
|
156
158
|
return false;
|
|
157
159
|
}
|
|
158
160
|
if (voucher.maxTokens !== parseMaxTokens(request.parameters)) {
|
|
@@ -163,31 +165,11 @@ function isValidPaymentVoucher(voucher, resourceFunction, manifest, request) {
|
|
|
163
165
|
if (isInferenceFunction(resourceFunction) || isInferenceManifest(manifest)) {
|
|
164
166
|
return false;
|
|
165
167
|
}
|
|
166
|
-
if (voucher.amount < manifest.
|
|
168
|
+
if (voucher.amount < manifest.costMicroUsd) {
|
|
167
169
|
return false;
|
|
168
170
|
}
|
|
169
171
|
return safeVerifyData(paymentVoucherPayload(voucher), voucher.publicKey, voucher.signature);
|
|
170
172
|
}
|
|
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
|
-
}
|
|
191
173
|
/**
|
|
192
174
|
* Encapsulates the routes
|
|
193
175
|
* @param fastify Encapsulated Fastify Instance
|
|
@@ -210,6 +192,32 @@ const routes = async (fastify, options) => {
|
|
|
210
192
|
return reply.status(401).send({ error: 'Invalid API Key' });
|
|
211
193
|
}
|
|
212
194
|
};
|
|
195
|
+
/**
|
|
196
|
+
* Requires `Authorization: Bearer <API_KEY>` or `Bearer <run token>`, a JWT from the operator addressed to
|
|
197
|
+
* this node. Records the caller on `request.runCaller`.
|
|
198
|
+
*/
|
|
199
|
+
const requireRunAuthorization = async (request, reply) => {
|
|
200
|
+
const authorizationHeader = request.headers['authorization'];
|
|
201
|
+
if (!authorizationHeader) {
|
|
202
|
+
return reply.status(401).send({ error: 'Authorization header is required' });
|
|
203
|
+
}
|
|
204
|
+
const token = authorizationHeader.split(' ')[1];
|
|
205
|
+
const expectedApiKey = options.service.config.apiKey;
|
|
206
|
+
if (token && expectedApiKey && secretsEqual(token, expectedApiKey)) {
|
|
207
|
+
request.runCaller = { kind: 'apiKey' };
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const verifier = options.service.runTokenVerifier;
|
|
211
|
+
if (token && verifier && looksLikeJwt(token)) {
|
|
212
|
+
const claims = await verifier.verify(token);
|
|
213
|
+
if (!claims) {
|
|
214
|
+
return reply.status(401).send({ error: 'Invalid run token' });
|
|
215
|
+
}
|
|
216
|
+
request.runCaller = { kind: 'client', token, ...claims };
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
return reply.status(401).send({ error: 'Invalid API Key' });
|
|
220
|
+
};
|
|
213
221
|
// Start Meta Routes
|
|
214
222
|
fastify.get('/', async (request, reply) => {
|
|
215
223
|
return {
|
|
@@ -255,12 +263,12 @@ const routes = async (fastify, options) => {
|
|
|
255
263
|
description: parameter.description,
|
|
256
264
|
})),
|
|
257
265
|
paymentManifest: !fn.paymentManifest ? undefined : isInferenceManifest(fn.paymentManifest) ? {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
266
|
+
inputCostMicroUsdPerMillionTokens: fn.paymentManifest.inputCostMicroUsdPerMillionTokens,
|
|
267
|
+
outputCostMicroUsdPerMillionTokens: fn.paymentManifest.outputCostMicroUsdPerMillionTokens,
|
|
268
|
+
recipientOrganizationId: fn.paymentManifest.recipientOrganizationId,
|
|
261
269
|
} : {
|
|
262
|
-
|
|
263
|
-
|
|
270
|
+
costMicroUsd: fn.paymentManifest.costMicroUsd,
|
|
271
|
+
recipientOrganizationId: fn.paymentManifest.recipientOrganizationId,
|
|
264
272
|
},
|
|
265
273
|
restricted: fn.restricted || undefined,
|
|
266
274
|
kind: isInferenceFunction(fn) ? 'inference' : undefined,
|
|
@@ -286,8 +294,14 @@ const routes = async (fastify, options) => {
|
|
|
286
294
|
});
|
|
287
295
|
// End Meta Routes
|
|
288
296
|
// Start Run Routes
|
|
297
|
+
/**
|
|
298
|
+
* Runs a request script. A run token from another organization's API client is billed: the operator
|
|
299
|
+
* holds the run's cost first, the script's paid calls draw on that hold, and afterwards the run is
|
|
300
|
+
* charged for the tokens the script executed at this node's listed price. Runs by the API key holder or
|
|
301
|
+
* by this node's own organization are free, and their paid calls are paid by this node's organization.
|
|
302
|
+
*/
|
|
289
303
|
// Authenticate before body validation, so unauthenticated callers learn nothing about the schema.
|
|
290
|
-
fastify.post('/run', { schema: { body: runRequestSchema }, preValidation:
|
|
304
|
+
fastify.post('/run', { schema: { body: runRequestSchema }, preValidation: requireRunAuthorization }, async (request, reply) => {
|
|
291
305
|
const runRequest = request.body;
|
|
292
306
|
let script;
|
|
293
307
|
try {
|
|
@@ -300,16 +314,50 @@ const routes = async (fastify, options) => {
|
|
|
300
314
|
if (script.declaration.kind !== 'request') {
|
|
301
315
|
return reply.status(400).send({ error: 'Invalid script' });
|
|
302
316
|
}
|
|
317
|
+
const caller = request.runCaller;
|
|
318
|
+
const billed = caller.kind === 'client' && caller.organizationId !== options.service.config.organizationId;
|
|
319
|
+
const runId = billed ? randomUUID() : undefined;
|
|
320
|
+
let resourceInvoker = options.service.resourceInvoker;
|
|
321
|
+
if (billed) {
|
|
322
|
+
const authorization = await options.service.operatorClient.authorizeRun({ token: caller.token, runId: runId, source: runRequest.source });
|
|
323
|
+
if (!authorization.ok) {
|
|
324
|
+
return reply.status(authorization.status).send({ error: authorization.error });
|
|
325
|
+
}
|
|
326
|
+
resourceInvoker = options.service.resourceInvoker.forRun({ runId: runId });
|
|
327
|
+
}
|
|
328
|
+
// Nothing waits on settlement, so it doesn't delay the response.
|
|
329
|
+
const settle = (outcome, tokens) => runId && setTimeout(async () => {
|
|
330
|
+
try {
|
|
331
|
+
if (outcome === 'capture') {
|
|
332
|
+
await options.service.operatorClient.captureRun({ runId, tokens });
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
await options.service.operatorClient.releaseRun({ runId });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
fastify.log.error(error, `Failed to ${outcome} run ${runId}`);
|
|
340
|
+
}
|
|
341
|
+
}, 0);
|
|
342
|
+
const meter = new TokenMeter();
|
|
303
343
|
try {
|
|
304
344
|
const result = await interpretParsed(script, {
|
|
305
345
|
resourceResolver: options.service.resourceResolver,
|
|
306
|
-
resourceInvoker
|
|
346
|
+
resourceInvoker,
|
|
347
|
+
meter,
|
|
307
348
|
});
|
|
308
|
-
|
|
349
|
+
settle('capture', meter.tokens);
|
|
350
|
+
return reply.status(200).send({ returnValue: result.returnValue ?? null, usage: { tokens: meter.tokens } });
|
|
309
351
|
}
|
|
310
352
|
catch (error) {
|
|
353
|
+
// A script that fails at runtime is the caller's mistake, and is charged for what it executed.
|
|
354
|
+
if (error instanceof RequestScriptError) {
|
|
355
|
+
settle('capture', meter.tokens);
|
|
356
|
+
return reply.status(422).send({ error: 'Script failed', details: error.message, usage: { tokens: meter.tokens } });
|
|
357
|
+
}
|
|
358
|
+
settle('release', meter.tokens);
|
|
311
359
|
fastify.log.error(error);
|
|
312
|
-
reply.status(500).send({ error: 'Internal server error' });
|
|
360
|
+
return reply.status(500).send({ error: 'Internal server error' });
|
|
313
361
|
}
|
|
314
362
|
});
|
|
315
363
|
/**
|
|
@@ -375,7 +423,7 @@ const routes = async (fastify, options) => {
|
|
|
375
423
|
if (maxTokens === undefined) {
|
|
376
424
|
return reply.status(400).send({ error: 'maxTokens must be a positive int32' });
|
|
377
425
|
}
|
|
378
|
-
const inputTokens = await estimateInputTokens(resourceFunction, resourceRequest);
|
|
426
|
+
const inputTokens = await estimateInputTokens(resourceFunction, resourceRequest.parameters);
|
|
379
427
|
if (inputTokens === undefined) {
|
|
380
428
|
return reply.status(500).send({ error: 'Failed to estimate input tokens' });
|
|
381
429
|
}
|
|
@@ -398,7 +446,7 @@ const routes = async (fastify, options) => {
|
|
|
398
446
|
if (!paymentVoucher) {
|
|
399
447
|
return reply.status(402).send({ error: 'Payment voucher is required' });
|
|
400
448
|
}
|
|
401
|
-
if (!isValidPaymentVoucher(paymentVoucher, resourceFunction, paymentManifest, resourceRequest)) {
|
|
449
|
+
if (!isValidPaymentVoucher(paymentVoucher, resourceFunction, paymentManifest, resourceRequest, options.service.config.organizationId)) {
|
|
402
450
|
return reply.status(400).send({ error: 'Invalid payment voucher' });
|
|
403
451
|
}
|
|
404
452
|
if (await options.service.grantService.isBlocked(paymentVoucher.publicKey)) {
|
|
@@ -406,45 +454,24 @@ const routes = async (fastify, options) => {
|
|
|
406
454
|
}
|
|
407
455
|
// Re-estimated here, so inference never runs on a voucher that can't cover the expected cost.
|
|
408
456
|
if (isInferenceVoucher(paymentVoucher)) {
|
|
409
|
-
const inputTokens = await estimateInputTokens(resourceFunction, resourceRequest);
|
|
457
|
+
const inputTokens = await estimateInputTokens(resourceFunction, resourceRequest.parameters);
|
|
410
458
|
if (inputTokens === undefined) {
|
|
411
459
|
return reply.status(500).send({ error: 'Failed to estimate input tokens' });
|
|
412
460
|
}
|
|
413
461
|
const requiredMaxAmount = inferenceCharge(paymentVoucher, inputTokens, paymentVoucher.maxTokens);
|
|
414
462
|
if (paymentVoucher.maxAmount < requiredMaxAmount) {
|
|
415
|
-
return reply.status(402).send({ error: `Payment voucher maxAmount ${paymentVoucher.maxAmount} is below the estimated ${requiredMaxAmount} micro-
|
|
463
|
+
return reply.status(402).send({ error: `Payment voucher maxAmount ${paymentVoucher.maxAmount} is below the estimated ${requiredMaxAmount} micro-USD`, requiredMaxAmount });
|
|
416
464
|
}
|
|
417
465
|
}
|
|
418
466
|
}
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
if (paymentManifest
|
|
422
|
-
|
|
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
|
-
}
|
|
467
|
+
const invoke = () => options.service.resourceInvoker.invoke(resource, resourceRequest.functionName, resourceRequest.parameters);
|
|
468
|
+
// A free function ignores any voucher sent with it.
|
|
469
|
+
if (!paymentManifest || !paymentVoucher) {
|
|
470
|
+
return invoke();
|
|
434
471
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const payment = settlement;
|
|
439
|
-
setTimeout(async () => {
|
|
440
|
-
// Nothing awaits this callback, so an uncaught error here would be an unhandled rejection.
|
|
441
|
-
try {
|
|
442
|
-
await options.service.operatorClient.settlePayment(payment);
|
|
443
|
-
}
|
|
444
|
-
catch (error) {
|
|
445
|
-
fastify.log.error(error, 'Failed to settle payment');
|
|
446
|
-
}
|
|
447
|
-
}, 0);
|
|
472
|
+
const result = await settlePaidCall(options.service.operatorClient, paymentVoucher, invoke, (error, message) => fastify.log.error(error, message));
|
|
473
|
+
if ('refused' in result) {
|
|
474
|
+
return reply.status(result.refused.status).send({ error: result.refused.error });
|
|
448
475
|
}
|
|
449
476
|
return result;
|
|
450
477
|
});
|
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
|
|
9
|
+
export declare const DEFAULT_COST_LIMIT_MICRO_USD = 10000;
|
|
10
10
|
export declare const DEFAULT_GRANT_REFRESH_INTERVAL_MS: number;
|
|
11
11
|
export interface StellariumNodeOptions {
|
|
12
12
|
port?: number;
|
|
@@ -20,17 +20,22 @@ export interface StellariumNodeOptions {
|
|
|
20
20
|
publicKey?: string;
|
|
21
21
|
/** Defaults to `SIGNING_PRIVATE_KEY`. */
|
|
22
22
|
privateKey?: string;
|
|
23
|
-
/** Bearer token for `POST /v1/run
|
|
23
|
+
/** Bearer token for `POST /v1/run` and the admin routes. Defaults to `API_KEY`. */
|
|
24
24
|
apiKey?: string;
|
|
25
25
|
/** Defaults to `OPERATOR_URL`. */
|
|
26
26
|
operatorUrl?: string;
|
|
27
|
-
/**
|
|
28
|
-
|
|
27
|
+
/**
|
|
28
|
+
* One-time token from the dashboard that enrolls this node in an organization. Defaults to
|
|
29
|
+
* `ENROLLMENT_TOKEN`. Only needed on a node's first start; an enrolled node ignores it.
|
|
30
|
+
*/
|
|
31
|
+
enrollmentToken?: string;
|
|
32
|
+
/** Most this node pays for one remote call. Defaults to `COST_LIMIT_MICRO_USD`, then 10,000 ($0.01). */
|
|
33
|
+
costLimitMicroUsd?: number;
|
|
29
34
|
/**
|
|
30
35
|
* Most this node signs for in one remote inference call (the voucher's `maxAmount`).
|
|
31
|
-
* Defaults to `
|
|
36
|
+
* Defaults to `INFERENCE_COST_LIMIT_MICRO_USD`, then to the resolved `costLimitMicroUsd`.
|
|
32
37
|
*/
|
|
33
|
-
|
|
38
|
+
inferenceCostLimitMicroUsd?: number;
|
|
34
39
|
/** SQLite file. Defaults to `./requestscript.db`; use ':memory:' for tests. */
|
|
35
40
|
databasePath?: string;
|
|
36
41
|
/**
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAErE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAiB,KAAK,UAAU,EAAgB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -4,7 +4,8 @@ import { validateInferenceFunction } from './resource/inference.js';
|
|
|
4
4
|
import routes from './routes.js';
|
|
5
5
|
import { createService } from './service.js';
|
|
6
6
|
import { StartupService } from './startup.js';
|
|
7
|
-
|
|
7
|
+
import { OperatorClient } from './client/operatorClient.js';
|
|
8
|
+
export const DEFAULT_COST_LIMIT_MICRO_USD = 10_000;
|
|
8
9
|
export const DEFAULT_GRANT_REFRESH_INTERVAL_MS = 1000 * 60 * 5;
|
|
9
10
|
function required(name, value) {
|
|
10
11
|
if (!value) {
|
|
@@ -16,13 +17,13 @@ function hostsPaidFunctions(resources) {
|
|
|
16
17
|
return resources.some(resource => resource.functions.some(fn => fn.paymentManifest));
|
|
17
18
|
}
|
|
18
19
|
export function resolveConfig(options, resources = []) {
|
|
19
|
-
const costLimit = options.
|
|
20
|
+
const costLimit = options.costLimitMicroUsd ?? (process.env.COST_LIMIT_MICRO_USD ? Number(process.env.COST_LIMIT_MICRO_USD) : DEFAULT_COST_LIMIT_MICRO_USD);
|
|
20
21
|
if (!Number.isSafeInteger(costLimit) || costLimit < 0) {
|
|
21
|
-
throw new Error('
|
|
22
|
+
throw new Error('costLimitMicroUsd must be a nonnegative integer');
|
|
22
23
|
}
|
|
23
|
-
const inferenceCostLimit = options.
|
|
24
|
+
const inferenceCostLimit = options.inferenceCostLimitMicroUsd ?? (process.env.INFERENCE_COST_LIMIT_MICRO_USD ? Number(process.env.INFERENCE_COST_LIMIT_MICRO_USD) : costLimit);
|
|
24
25
|
if (!Number.isSafeInteger(inferenceCostLimit) || inferenceCostLimit < 0) {
|
|
25
|
-
throw new Error('
|
|
26
|
+
throw new Error('inferenceCostLimitMicroUsd must be a nonnegative integer');
|
|
26
27
|
}
|
|
27
28
|
for (const resource of resources) {
|
|
28
29
|
resource.functions.forEach(validateInferenceFunction);
|
|
@@ -36,8 +37,8 @@ export function resolveConfig(options, resources = []) {
|
|
|
36
37
|
operatorUrl: hostsPaidFunctions(resources)
|
|
37
38
|
? required('OPERATOR_URL', options.operatorUrl ?? process.env.OPERATOR_URL)
|
|
38
39
|
: options.operatorUrl ?? process.env.OPERATOR_URL,
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
costLimitMicroUsd: costLimit,
|
|
41
|
+
inferenceCostLimitMicroUsd: inferenceCostLimit,
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
44
|
export class StellariumNode {
|
|
@@ -52,6 +53,30 @@ export class StellariumNode {
|
|
|
52
53
|
*/
|
|
53
54
|
async start(fastify, options) {
|
|
54
55
|
const config = resolveConfig(options, this.resources);
|
|
56
|
+
const enrollmentToken = options.enrollmentToken ?? process.env.ENROLLMENT_TOKEN;
|
|
57
|
+
// Paid functions are paid to the node's organization, so a provider must be enrolled before it serves them.
|
|
58
|
+
// A node that only calls paid functions enrolls the same way, so its vouchers draw on its organization's balance.
|
|
59
|
+
// Any other node with an operator still reports its URL, for listing, and learns its organization, so its
|
|
60
|
+
// own organization's run tokens run free.
|
|
61
|
+
const session = () => new OperatorClient(config.operatorUrl, config.publicKey, config.privateKey).session(enrollmentToken, config.baseUrl);
|
|
62
|
+
if (hostsPaidFunctions(this.resources) || enrollmentToken) {
|
|
63
|
+
try {
|
|
64
|
+
({ organizationId: config.organizationId } = await session());
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
const hint = enrollmentToken ? '' : '. Create an enrollment token for it at https://platform.stellarium.sh and set ENROLLMENT_TOKEN';
|
|
68
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)}${hint}`, { cause: error });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else if (config.operatorUrl) {
|
|
72
|
+
// Serving doesn't depend on it, so it doesn't hold up the start. Until it answers, run tokens from
|
|
73
|
+
// this node's own organization are refused as if billed.
|
|
74
|
+
session().then(({ organizationId }) => {
|
|
75
|
+
config.organizationId = organizationId;
|
|
76
|
+
}, (error) => {
|
|
77
|
+
fastify.log.warn(`No session with the operator, so this node can't be listed: ${error instanceof Error ? error.message : String(error)}`);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
55
80
|
const db = await openDatabase(options.databasePath ?? DEFAULT_DATABASE_PATH);
|
|
56
81
|
fastify.addHook('onClose', async () => db.close());
|
|
57
82
|
const service = createService(db, this.resources, config, { approveGrant: options.approveGrant });
|
package/dist/service.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { JWTVerifyGetKey } from "jose";
|
|
2
|
+
import { ResourceResolver } from "requestscript";
|
|
2
3
|
import type { Database } from "sqlite";
|
|
3
4
|
import { PeerRepository } from "./peer/repository.js";
|
|
4
5
|
import { ResourceRepository, type StellariumResource } from "./resource/repository.js";
|
|
6
|
+
import { CombinationResourceInvoker } from "./resource/invoker.js";
|
|
5
7
|
import { OperatorClient } from "./client/operatorClient.js";
|
|
8
|
+
import { RunTokenVerifier } from "./client/runTokens.js";
|
|
6
9
|
import { type ApproveGrant, GrantService } from "./grant/service.js";
|
|
7
10
|
/** Settings a node runs with. `StellariumNode.start` fills these from its options, then the environment. */
|
|
8
11
|
export interface NodeConfig {
|
|
@@ -12,27 +15,36 @@ export interface NodeConfig {
|
|
|
12
15
|
publicKey: string;
|
|
13
16
|
/** Ed25519 PKCS#8 DER, base64url. */
|
|
14
17
|
privateKey: string;
|
|
15
|
-
/** Bearer token for `POST /v1/run
|
|
18
|
+
/** Bearer token for `POST /v1/run` and the admin routes. When unset, only run tokens are accepted, and the admin routes reject every request. */
|
|
16
19
|
apiKey?: string;
|
|
17
|
-
/** Operator base URL, including `/v1`. Required when the node hosts paid functions. */
|
|
20
|
+
/** Operator base URL, including `/v1`. Required when the node hosts paid functions, and to accept run tokens. */
|
|
18
21
|
operatorUrl?: string;
|
|
22
|
+
/**
|
|
23
|
+
* The operator organization this node is enrolled in, which is paid for its functions.
|
|
24
|
+
* `StellariumNode.start` asks the operator for it when the node hosts paid functions.
|
|
25
|
+
*/
|
|
26
|
+
organizationId?: string;
|
|
19
27
|
/** Most this node will pay for one remote call. */
|
|
20
|
-
|
|
28
|
+
costLimitMicroUsd: number;
|
|
21
29
|
/** Most this node will sign for in one remote inference call. */
|
|
22
|
-
|
|
30
|
+
inferenceCostLimitMicroUsd: number;
|
|
23
31
|
}
|
|
24
32
|
export interface Service {
|
|
25
33
|
config: NodeConfig;
|
|
26
34
|
peerRepository: PeerRepository;
|
|
27
35
|
resourceRepository: ResourceRepository;
|
|
28
36
|
resourceResolver: ResourceResolver;
|
|
29
|
-
resourceInvoker:
|
|
37
|
+
resourceInvoker: CombinationResourceInvoker;
|
|
30
38
|
operatorClient: OperatorClient;
|
|
31
39
|
grantService: GrantService;
|
|
40
|
+
/** Verifies the run tokens `POST /v1/run` accepts. Set when the node has an operator URL. */
|
|
41
|
+
runTokenVerifier?: RunTokenVerifier;
|
|
32
42
|
}
|
|
33
43
|
export interface ServiceOptions {
|
|
34
44
|
/** Decides `POST /v1/grants/request`. Without it, every request is refused. */
|
|
35
45
|
approveGrant?: ApproveGrant;
|
|
46
|
+
/** Replaces the operator's published keys for verifying run tokens, in tests. */
|
|
47
|
+
runTokenKeys?: JWTVerifyGetKey;
|
|
36
48
|
}
|
|
37
49
|
export declare function createService(db: Database, resources: StellariumResource[], config: NodeConfig, options?: ServiceOptions): Service;
|
|
38
50
|
//# sourceMappingURL=service.d.ts.map
|
package/dist/service.d.ts.map
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,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;AACvF,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,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,iJAAiJ;IACjJ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iHAAiH;IACjH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iEAAiE;IACjE,0BAA0B,EAAE,MAAM,CAAC;CACtC;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,0BAA0B,CAAC;IAC5C,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,YAAY,CAAC;IAC3B,6FAA6F;IAC7F,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACvC;AAED,MAAM,WAAW,cAAc;IAC3B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,iFAAiF;IACjF,YAAY,CAAC,EAAE,eAAe,CAAC;CAClC;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAwBtI"}
|
package/dist/service.js
CHANGED
|
@@ -3,14 +3,18 @@ import { CombinationResourceResolver } from "./resource/resolver.js";
|
|
|
3
3
|
import { ResourceRepository } from "./resource/repository.js";
|
|
4
4
|
import { CombinationResourceInvoker } from "./resource/invoker.js";
|
|
5
5
|
import { OperatorClient } from "./client/operatorClient.js";
|
|
6
|
+
import { RunTokenVerifier } from "./client/runTokens.js";
|
|
6
7
|
import { GrantRepository } from "./grant/repository.js";
|
|
7
8
|
import { GrantService } from "./grant/service.js";
|
|
8
9
|
export function createService(db, resources, config, options = {}) {
|
|
9
|
-
const resourceRepository = new ResourceRepository(db, resources, config.baseUrl);
|
|
10
|
+
const resourceRepository = new ResourceRepository(db, resources, config.baseUrl, config.organizationId);
|
|
10
11
|
const resourceResolver = new CombinationResourceResolver(resourceRepository);
|
|
11
12
|
const grantService = new GrantService(new GrantRepository(db), config, options.approveGrant);
|
|
12
|
-
const
|
|
13
|
-
const
|
|
13
|
+
const operatorClient = new OperatorClient(config.operatorUrl, config.publicKey, config.privateKey);
|
|
14
|
+
const resourceInvoker = new CombinationResourceInvoker(resourceRepository, config.costLimitMicroUsd, config.publicKey, config.privateKey, grantService, config.inferenceCostLimitMicroUsd, {
|
|
15
|
+
operatorClient,
|
|
16
|
+
organizationId: config.organizationId,
|
|
17
|
+
});
|
|
14
18
|
return {
|
|
15
19
|
config,
|
|
16
20
|
peerRepository: new PeerRepository(db),
|
|
@@ -19,5 +23,6 @@ export function createService(db, resources, config, options = {}) {
|
|
|
19
23
|
resourceInvoker,
|
|
20
24
|
operatorClient,
|
|
21
25
|
grantService,
|
|
26
|
+
runTokenVerifier: config.operatorUrl ? new RunTokenVerifier(config.operatorUrl, config.publicKey, options.runTokenKeys) : undefined,
|
|
22
27
|
};
|
|
23
28
|
}
|