stellarium-ts 0.16.0 → 0.17.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 +68 -31
- package/dist/client/operatorClient.d.ts.map +1 -1
- package/dist/client/operatorClient.js +82 -23
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +8 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/resource/inference.d.ts +3 -3
- package/dist/resource/inference.d.ts.map +1 -1
- package/dist/resource/inference.js +7 -7
- package/dist/resource/invoker.d.ts +13 -13
- package/dist/resource/invoker.d.ts.map +1 -1
- package/dist/resource/invoker.js +26 -23
- 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/routes.d.ts.map +1 -1
- package/dist/routes.js +46 -37
- package/dist/server.d.ts +10 -5
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +20 -7
- package/dist/service.d.ts +7 -2
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +3 -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 +56 -9
- package/dist/tests/resource/invoker.test.js +14 -9
- package/dist/tests/resource/repository.test.js +9 -9
- package/dist/tests/routes.test.js +71 -38
- package/dist/tests/server.test.js +15 -15
- package/package.json +1 -1
package/dist/resource/invoker.js
CHANGED
|
@@ -13,16 +13,16 @@ export function invocationHash(resourcePath, resourceName, functionName, paramet
|
|
|
13
13
|
* Key order is part of the signed bytes.
|
|
14
14
|
*/
|
|
15
15
|
export function paymentVoucherPayload(voucher) {
|
|
16
|
-
const { requestId, invocationId, amount,
|
|
17
|
-
return { requestId, invocationId, amount,
|
|
16
|
+
const { requestId, invocationId, amount, recipientOrganizationId, expiresAt, invocationHash, publicKey } = voucher;
|
|
17
|
+
return { requestId, invocationId, amount, recipientOrganizationId, expiresAt, invocationHash, publicKey };
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* The object `createInferenceVoucher` signs: every inference voucher field except `signature`.
|
|
21
21
|
* Key order is part of the signed bytes.
|
|
22
22
|
*/
|
|
23
23
|
export function inferenceVoucherPayload(voucher) {
|
|
24
|
-
const { kind, requestId, invocationId, maxAmount,
|
|
25
|
-
return { kind, requestId, invocationId, maxAmount,
|
|
24
|
+
const { kind, requestId, invocationId, maxAmount, inputCostMicroUsdPerMillionTokens, outputCostMicroUsdPerMillionTokens, maxTokens, recipientOrganizationId, expiresAt, invocationHash, publicKey } = voucher;
|
|
25
|
+
return { kind, requestId, invocationId, maxAmount, inputCostMicroUsdPerMillionTokens, outputCostMicroUsdPerMillionTokens, maxTokens, recipientOrganizationId, expiresAt, invocationHash, publicKey };
|
|
26
26
|
}
|
|
27
27
|
export function isInferenceVoucher(voucher) {
|
|
28
28
|
return voucher.kind === 'inference';
|
|
@@ -63,22 +63,22 @@ async function fetchWithRetry(url, init, timeoutMs = EXTERNAL_INVOKE_TIMEOUT_MS)
|
|
|
63
63
|
}
|
|
64
64
|
export class CombinationResourceInvoker {
|
|
65
65
|
resourceRepository;
|
|
66
|
-
|
|
66
|
+
costLimitMicroUsd;
|
|
67
67
|
publicKey;
|
|
68
68
|
privateKey;
|
|
69
69
|
grantService;
|
|
70
|
-
|
|
70
|
+
inferenceCostLimitMicroUsd;
|
|
71
71
|
/**
|
|
72
72
|
* Without a `grantService`, calls to restricted remote functions fail.
|
|
73
|
-
* `
|
|
73
|
+
* `inferenceCostLimitMicroUsd` caps an inference voucher's `maxAmount`, and defaults to `costLimitMicroUsd`.
|
|
74
74
|
*/
|
|
75
|
-
constructor(resourceRepository,
|
|
75
|
+
constructor(resourceRepository, costLimitMicroUsd, publicKey, privateKey, grantService, inferenceCostLimitMicroUsd = costLimitMicroUsd) {
|
|
76
76
|
this.resourceRepository = resourceRepository;
|
|
77
|
-
this.
|
|
77
|
+
this.costLimitMicroUsd = costLimitMicroUsd;
|
|
78
78
|
this.publicKey = publicKey;
|
|
79
79
|
this.privateKey = privateKey;
|
|
80
80
|
this.grantService = grantService;
|
|
81
|
-
this.
|
|
81
|
+
this.inferenceCostLimitMicroUsd = inferenceCostLimitMicroUsd;
|
|
82
82
|
}
|
|
83
83
|
/**
|
|
84
84
|
* Invoke a function on a resource.
|
|
@@ -117,11 +117,14 @@ export class CombinationResourceInvoker {
|
|
|
117
117
|
}
|
|
118
118
|
const manifest = resourceFunction.paymentManifest;
|
|
119
119
|
const inference = isInferenceFunction(resourceFunction);
|
|
120
|
+
if (manifest && !manifest.recipientOrganizationId) {
|
|
121
|
+
return { value: null, error: { message: `Failed to invoke ${resource.name}.${functionName}: its payment manifest names no recipient organization` } };
|
|
122
|
+
}
|
|
120
123
|
if (manifest && inference) {
|
|
121
124
|
console.log(`Resource function ${resource.name}.${functionName} is a paid inference function, creating payment voucher...`);
|
|
122
125
|
// Peers' manifests aren't validated on receipt.
|
|
123
|
-
if (!isInferenceManifest(manifest) || !Number.isSafeInteger(manifest.
|
|
124
|
-
|| !Number.isSafeInteger(manifest.
|
|
126
|
+
if (!isInferenceManifest(manifest) || !Number.isSafeInteger(manifest.inputCostMicroUsdPerMillionTokens) || manifest.inputCostMicroUsdPerMillionTokens < 0
|
|
127
|
+
|| !Number.isSafeInteger(manifest.outputCostMicroUsdPerMillionTokens) || manifest.outputCostMicroUsdPerMillionTokens < 0) {
|
|
125
128
|
return { value: null, error: { message: `Failed to invoke ${resource.name}.${functionName}: its payment manifest has no valid per-token pricing` } };
|
|
126
129
|
}
|
|
127
130
|
const maxTokens = parseMaxTokens(parameters);
|
|
@@ -136,8 +139,8 @@ export class CombinationResourceInvoker {
|
|
|
136
139
|
if (inferenceCharge(manifest, estimate.inputTokens, maxTokens) !== estimate.maxAmount) {
|
|
137
140
|
return { value: null, error: { message: `Failed to invoke ${resource.name}.${functionName}: the provider's estimate doesn't match its advertised rates` } };
|
|
138
141
|
}
|
|
139
|
-
if (estimate.maxAmount > this.
|
|
140
|
-
throw new Error(`Function ${functionName} may cost up to ${estimate.maxAmount} micro-
|
|
142
|
+
if (estimate.maxAmount > this.inferenceCostLimitMicroUsd) {
|
|
143
|
+
throw new Error(`Function ${functionName} may cost up to ${estimate.maxAmount} micro-USD, which exceeds the inference cost limit of ${this.inferenceCostLimitMicroUsd} micro-USD`);
|
|
141
144
|
}
|
|
142
145
|
requestBody.paymentVoucher = this.createInferenceVoucher(manifest, estimate.maxAmount, maxTokens, resource.path, resource.name, functionName, parameters);
|
|
143
146
|
}
|
|
@@ -146,10 +149,10 @@ export class CombinationResourceInvoker {
|
|
|
146
149
|
if (isInferenceManifest(manifest)) {
|
|
147
150
|
return { value: null, error: { message: `Failed to invoke ${resource.name}.${functionName}: it has per-token pricing but isn't an inference function` } };
|
|
148
151
|
}
|
|
149
|
-
if (manifest.
|
|
150
|
-
throw new Error(`Function ${functionName} cost ${manifest.
|
|
152
|
+
if (manifest.costMicroUsd > this.costLimitMicroUsd) {
|
|
153
|
+
throw new Error(`Function ${functionName} cost ${manifest.costMicroUsd} micro-USD, which exceeds the cost limit of ${this.costLimitMicroUsd} micro-USD`);
|
|
151
154
|
}
|
|
152
|
-
requestBody.paymentVoucher = this.createPaymentVoucher(manifest.
|
|
155
|
+
requestBody.paymentVoucher = this.createPaymentVoucher(manifest.recipientOrganizationId, manifest.costMicroUsd, resource.path, resource.name, functionName, parameters);
|
|
153
156
|
}
|
|
154
157
|
// The voucher is created once and reused on every attempt, so the operator's
|
|
155
158
|
// requestId/invocationId check prevents a retried call from being settled twice.
|
|
@@ -210,13 +213,13 @@ export class CombinationResourceInvoker {
|
|
|
210
213
|
}
|
|
211
214
|
return { inputTokens: inputTokens, maxAmount: maxAmount };
|
|
212
215
|
}
|
|
213
|
-
createPaymentVoucher(
|
|
216
|
+
createPaymentVoucher(recipientOrganizationId, amount, resourcePath, resourceName, functionName, parameters) {
|
|
214
217
|
const requestId = crypto.randomUUID();
|
|
215
218
|
const invocationId = crypto.randomUUID();
|
|
216
219
|
const expiresAt = new Date(Date.now() + PAYMENT_VOUCHER_LIFETIME_MS).toISOString();
|
|
217
220
|
const hash = invocationHash(resourcePath, resourceName, functionName, parameters);
|
|
218
|
-
const signature = signData(paymentVoucherPayload({ requestId, invocationId, amount,
|
|
219
|
-
return { requestId, invocationId, amount,
|
|
221
|
+
const signature = signData(paymentVoucherPayload({ requestId, invocationId, amount, recipientOrganizationId, expiresAt, invocationHash: hash, publicKey: this.publicKey }), this.privateKey);
|
|
222
|
+
return { requestId, invocationId, amount, recipientOrganizationId, signature, expiresAt, invocationHash: hash, publicKey: this.publicKey };
|
|
220
223
|
}
|
|
221
224
|
createInferenceVoucher(manifest, maxAmount, maxTokens, resourcePath, resourceName, functionName, parameters) {
|
|
222
225
|
const unsigned = {
|
|
@@ -224,10 +227,10 @@ export class CombinationResourceInvoker {
|
|
|
224
227
|
requestId: crypto.randomUUID(),
|
|
225
228
|
invocationId: crypto.randomUUID(),
|
|
226
229
|
maxAmount,
|
|
227
|
-
|
|
228
|
-
|
|
230
|
+
inputCostMicroUsdPerMillionTokens: manifest.inputCostMicroUsdPerMillionTokens,
|
|
231
|
+
outputCostMicroUsdPerMillionTokens: manifest.outputCostMicroUsdPerMillionTokens,
|
|
229
232
|
maxTokens,
|
|
230
|
-
|
|
233
|
+
recipientOrganizationId: manifest.recipientOrganizationId,
|
|
231
234
|
expiresAt: new Date(Date.now() + INFERENCE_VOUCHER_LIFETIME_MS).toISOString(),
|
|
232
235
|
invocationHash: invocationHash(resourcePath, resourceName, functionName, parameters),
|
|
233
236
|
publicKey: this.publicKey,
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { Resource, ResourceFunction, ResourceFunctionCallParameter, ResourceFunctionParameter } from "requestscript";
|
|
2
2
|
import { Database } from "sqlite";
|
|
3
3
|
import type { StoredPeer } from "../peer/repository.js";
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* A flat price per call. `recipientOrganizationId` is filled in by the hosting node when it advertises
|
|
6
|
+
* the function, from the organization it is enrolled in; resource authors leave it out.
|
|
7
|
+
*/
|
|
5
8
|
export interface StellariumFixedPaymentManifest {
|
|
6
9
|
id?: number;
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
costMicroUsd: number;
|
|
11
|
+
recipientOrganizationId?: string;
|
|
9
12
|
}
|
|
10
13
|
/** Per-token pricing for an inference function. Either rate may be 0. */
|
|
11
14
|
export interface StellariumInferencePaymentManifest {
|
|
12
15
|
id?: number;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
inputCostMicroUsdPerMillionTokens: number;
|
|
17
|
+
outputCostMicroUsdPerMillionTokens: number;
|
|
18
|
+
recipientOrganizationId?: string;
|
|
16
19
|
}
|
|
17
20
|
export type StellariumResourceFunctionPaymentManifest = StellariumFixedPaymentManifest | StellariumInferencePaymentManifest;
|
|
18
21
|
/** Fixed functions return one value. Inference functions run AI inference and are priced per token. */
|
|
@@ -61,8 +64,10 @@ export declare class ResourceRepository {
|
|
|
61
64
|
private readonly db;
|
|
62
65
|
private resources;
|
|
63
66
|
private readonly baseUrl;
|
|
67
|
+
private readonly organizationId?;
|
|
64
68
|
private writes;
|
|
65
|
-
|
|
69
|
+
/** `organizationId` is the operator organization this node is enrolled in, which is paid for its functions. */
|
|
70
|
+
constructor(db: Database, resources?: StellariumResource[], baseUrl?: string, organizationId?: string | undefined);
|
|
66
71
|
private serialize;
|
|
67
72
|
private transaction;
|
|
68
73
|
private isInternal;
|
|
@@ -84,6 +89,7 @@ export declare class ResourceRepository {
|
|
|
84
89
|
private deleteFunctions;
|
|
85
90
|
getInternalResource(path: string, name: string): Promise<StellariumResource | undefined>;
|
|
86
91
|
getResource(key: string): Promise<Resource | undefined>;
|
|
92
|
+
/** This node's resources, with its base URL and its organization as the recipient of every paid function. */
|
|
87
93
|
getInternalResources(): Promise<StellariumResource[]>;
|
|
88
94
|
/** Learned resources in insertion order. A negative `limit` means no limit. */
|
|
89
95
|
getExternalResources(offset?: number, limit?: number): Promise<StellariumResource[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/resource/repository.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,6BAA6B,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AACrH,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGxD
|
|
1
|
+
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/resource/repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,12 +24,15 @@ export class ResourceRepository {
|
|
|
24
24
|
db;
|
|
25
25
|
resources;
|
|
26
26
|
baseUrl;
|
|
27
|
+
organizationId;
|
|
27
28
|
// The connection is shared, so transactions are serialized to keep them from nesting.
|
|
28
29
|
writes = Promise.resolve();
|
|
29
|
-
|
|
30
|
+
/** `organizationId` is the operator organization this node is enrolled in, which is paid for its functions. */
|
|
31
|
+
constructor(db, resources = [], baseUrl = '', organizationId) {
|
|
30
32
|
this.db = db;
|
|
31
33
|
this.resources = resources;
|
|
32
34
|
this.baseUrl = baseUrl;
|
|
35
|
+
this.organizationId = organizationId;
|
|
33
36
|
}
|
|
34
37
|
serialize(work) {
|
|
35
38
|
const run = this.writes.then(work, work);
|
|
@@ -123,12 +126,12 @@ export class ResourceRepository {
|
|
|
123
126
|
const manifest = fn.paymentManifest;
|
|
124
127
|
if (manifest) {
|
|
125
128
|
const inference = isInferenceManifest(manifest);
|
|
126
|
-
await this.db.run('INSERT INTO resource_function_payment_manifest (resource_function_id,
|
|
129
|
+
await this.db.run('INSERT INTO resource_function_payment_manifest (resource_function_id, cost_micro_usd, input_cost_micro_usd_per_million_tokens, output_cost_micro_usd_per_million_tokens, recipient_organization_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)', [
|
|
127
130
|
functionId,
|
|
128
|
-
inference ? null : manifest.
|
|
129
|
-
inference ? manifest.
|
|
130
|
-
inference ? manifest.
|
|
131
|
-
manifest.
|
|
131
|
+
inference ? null : manifest.costMicroUsd,
|
|
132
|
+
inference ? manifest.inputCostMicroUsdPerMillionTokens : null,
|
|
133
|
+
inference ? manifest.outputCostMicroUsdPerMillionTokens : null,
|
|
134
|
+
manifest.recipientOrganizationId ?? null,
|
|
132
135
|
createdAt,
|
|
133
136
|
createdAt,
|
|
134
137
|
]);
|
|
@@ -167,8 +170,15 @@ export class ResourceRepository {
|
|
|
167
170
|
}
|
|
168
171
|
};
|
|
169
172
|
}
|
|
173
|
+
/** This node's resources, with its base URL and its organization as the recipient of every paid function. */
|
|
170
174
|
async getInternalResources() {
|
|
171
|
-
return this.resources.map(r => ({
|
|
175
|
+
return this.resources.map(r => ({
|
|
176
|
+
...r,
|
|
177
|
+
baseUrl: this.baseUrl,
|
|
178
|
+
functions: r.functions.map((fn) => fn.paymentManifest
|
|
179
|
+
? { ...fn, paymentManifest: { ...fn.paymentManifest, recipientOrganizationId: this.organizationId } }
|
|
180
|
+
: fn),
|
|
181
|
+
}));
|
|
172
182
|
}
|
|
173
183
|
/** Learned resources in insertion order. A negative `limit` means no limit. */
|
|
174
184
|
async getExternalResources(offset = 0, limit = -1) {
|
|
@@ -231,18 +241,18 @@ export class ResourceRepository {
|
|
|
231
241
|
if (!result) {
|
|
232
242
|
return undefined;
|
|
233
243
|
}
|
|
234
|
-
if (result.
|
|
244
|
+
if (result.cost_micro_usd === null) {
|
|
235
245
|
return {
|
|
236
246
|
id: result.id,
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
247
|
+
inputCostMicroUsdPerMillionTokens: result.input_cost_micro_usd_per_million_tokens,
|
|
248
|
+
outputCostMicroUsdPerMillionTokens: result.output_cost_micro_usd_per_million_tokens,
|
|
249
|
+
recipientOrganizationId: result.recipient_organization_id ?? undefined,
|
|
240
250
|
};
|
|
241
251
|
}
|
|
242
252
|
return {
|
|
243
253
|
id: result.id,
|
|
244
|
-
|
|
245
|
-
|
|
254
|
+
costMicroUsd: result.cost_micro_usd,
|
|
255
|
+
recipientOrganizationId: result.recipient_organization_id ?? undefined,
|
|
246
256
|
};
|
|
247
257
|
}
|
|
248
258
|
/**
|
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;AAMhF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;
|
|
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;AAM5C,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,CAgY5C,CAAC;eAGa,MAAM"}
|
package/dist/routes.js
CHANGED
|
@@ -62,12 +62,12 @@ const resourceCallProperties = {
|
|
|
62
62
|
};
|
|
63
63
|
const fixedVoucherSchema = {
|
|
64
64
|
type: 'object',
|
|
65
|
-
required: ['requestId', 'invocationId', 'amount', '
|
|
65
|
+
required: ['requestId', 'invocationId', 'amount', 'recipientOrganizationId', 'signature', 'expiresAt', 'invocationHash', 'publicKey'],
|
|
66
66
|
properties: {
|
|
67
67
|
requestId: { type: 'string' },
|
|
68
68
|
invocationId: { type: 'string' },
|
|
69
69
|
amount: { type: 'integer' },
|
|
70
|
-
|
|
70
|
+
recipientOrganizationId: { type: 'string' },
|
|
71
71
|
signature: { type: 'string' },
|
|
72
72
|
expiresAt: { type: 'string' },
|
|
73
73
|
invocationHash: { type: 'string' },
|
|
@@ -76,16 +76,16 @@ const fixedVoucherSchema = {
|
|
|
76
76
|
};
|
|
77
77
|
const inferenceVoucherSchema = {
|
|
78
78
|
type: 'object',
|
|
79
|
-
required: ['kind', 'requestId', 'invocationId', 'maxAmount', '
|
|
79
|
+
required: ['kind', 'requestId', 'invocationId', 'maxAmount', 'inputCostMicroUsdPerMillionTokens', 'outputCostMicroUsdPerMillionTokens', 'maxTokens', 'recipientOrganizationId', 'signature', 'expiresAt', 'invocationHash', 'publicKey'],
|
|
80
80
|
properties: {
|
|
81
81
|
kind: { const: 'inference' },
|
|
82
82
|
requestId: { type: 'string' },
|
|
83
83
|
invocationId: { type: 'string' },
|
|
84
84
|
maxAmount: { type: 'integer', minimum: 0 },
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
inputCostMicroUsdPerMillionTokens: { type: 'integer', minimum: 0 },
|
|
86
|
+
outputCostMicroUsdPerMillionTokens: { type: 'integer', minimum: 0 },
|
|
87
87
|
maxTokens: { type: 'integer', minimum: 1 },
|
|
88
|
-
|
|
88
|
+
recipientOrganizationId: { type: 'string' },
|
|
89
89
|
signature: { type: 'string' },
|
|
90
90
|
expiresAt: { type: 'string' },
|
|
91
91
|
invocationHash: { type: 'string' },
|
|
@@ -131,16 +131,16 @@ function secretsEqual(a, b) {
|
|
|
131
131
|
}
|
|
132
132
|
/**
|
|
133
133
|
* A voucher is valid when it is bound to this exact call, signed by its public key, unexpired,
|
|
134
|
-
* and pays
|
|
134
|
+
* and pays this node's organization. A fixed voucher must pay at least the function's cost. An inference
|
|
135
135
|
* voucher must be for an inference function, carry its rates, and sign the call's `maxTokens`.
|
|
136
136
|
* Whether its `maxAmount` covers the call is checked separately, against the function's estimator.
|
|
137
137
|
*/
|
|
138
|
-
function isValidPaymentVoucher(voucher, resourceFunction, manifest, request) {
|
|
138
|
+
function isValidPaymentVoucher(voucher, resourceFunction, manifest, request, organizationId) {
|
|
139
139
|
const expectedHash = invocationHash(request.resourcePath, request.resourceName, request.functionName, request.parameters);
|
|
140
140
|
if (voucher.invocationHash !== expectedHash) {
|
|
141
141
|
return false;
|
|
142
142
|
}
|
|
143
|
-
if (voucher.
|
|
143
|
+
if (!organizationId || voucher.recipientOrganizationId !== organizationId) {
|
|
144
144
|
return false;
|
|
145
145
|
}
|
|
146
146
|
const expiresAt = Date.parse(voucher.expiresAt);
|
|
@@ -151,8 +151,8 @@ function isValidPaymentVoucher(voucher, resourceFunction, manifest, request) {
|
|
|
151
151
|
if (!isInferenceFunction(resourceFunction) || !isInferenceManifest(manifest)) {
|
|
152
152
|
return false;
|
|
153
153
|
}
|
|
154
|
-
if (voucher.
|
|
155
|
-
|| voucher.
|
|
154
|
+
if (voucher.inputCostMicroUsdPerMillionTokens !== manifest.inputCostMicroUsdPerMillionTokens
|
|
155
|
+
|| voucher.outputCostMicroUsdPerMillionTokens !== manifest.outputCostMicroUsdPerMillionTokens) {
|
|
156
156
|
return false;
|
|
157
157
|
}
|
|
158
158
|
if (voucher.maxTokens !== parseMaxTokens(request.parameters)) {
|
|
@@ -163,7 +163,7 @@ function isValidPaymentVoucher(voucher, resourceFunction, manifest, request) {
|
|
|
163
163
|
if (isInferenceFunction(resourceFunction) || isInferenceManifest(manifest)) {
|
|
164
164
|
return false;
|
|
165
165
|
}
|
|
166
|
-
if (voucher.amount < manifest.
|
|
166
|
+
if (voucher.amount < manifest.costMicroUsd) {
|
|
167
167
|
return false;
|
|
168
168
|
}
|
|
169
169
|
return safeVerifyData(paymentVoucherPayload(voucher), voucher.publicKey, voucher.signature);
|
|
@@ -178,7 +178,7 @@ async function estimateInputTokens(resourceFunction, request) {
|
|
|
178
178
|
return undefined;
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
|
-
/** What the operator will
|
|
181
|
+
/** What the operator will capture for `usage`: its charge at the voucher's rates, capped at `maxAmount`. */
|
|
182
182
|
function cappedInferenceCharge(voucher, usage) {
|
|
183
183
|
try {
|
|
184
184
|
return Math.min(inferenceCharge(voucher, usage.inputTokens, usage.outputTokens), voucher.maxAmount);
|
|
@@ -255,12 +255,12 @@ const routes = async (fastify, options) => {
|
|
|
255
255
|
description: parameter.description,
|
|
256
256
|
})),
|
|
257
257
|
paymentManifest: !fn.paymentManifest ? undefined : isInferenceManifest(fn.paymentManifest) ? {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
258
|
+
inputCostMicroUsdPerMillionTokens: fn.paymentManifest.inputCostMicroUsdPerMillionTokens,
|
|
259
|
+
outputCostMicroUsdPerMillionTokens: fn.paymentManifest.outputCostMicroUsdPerMillionTokens,
|
|
260
|
+
recipientOrganizationId: fn.paymentManifest.recipientOrganizationId,
|
|
261
261
|
} : {
|
|
262
|
-
|
|
263
|
-
|
|
262
|
+
costMicroUsd: fn.paymentManifest.costMicroUsd,
|
|
263
|
+
recipientOrganizationId: fn.paymentManifest.recipientOrganizationId,
|
|
264
264
|
},
|
|
265
265
|
restricted: fn.restricted || undefined,
|
|
266
266
|
kind: isInferenceFunction(fn) ? 'inference' : undefined,
|
|
@@ -398,7 +398,7 @@ const routes = async (fastify, options) => {
|
|
|
398
398
|
if (!paymentVoucher) {
|
|
399
399
|
return reply.status(402).send({ error: 'Payment voucher is required' });
|
|
400
400
|
}
|
|
401
|
-
if (!isValidPaymentVoucher(paymentVoucher, resourceFunction, paymentManifest, resourceRequest)) {
|
|
401
|
+
if (!isValidPaymentVoucher(paymentVoucher, resourceFunction, paymentManifest, resourceRequest, options.service.config.organizationId)) {
|
|
402
402
|
return reply.status(400).send({ error: 'Invalid payment voucher' });
|
|
403
403
|
}
|
|
404
404
|
if (await options.service.grantService.isBlocked(paymentVoucher.publicKey)) {
|
|
@@ -412,37 +412,46 @@ const routes = async (fastify, options) => {
|
|
|
412
412
|
}
|
|
413
413
|
const requiredMaxAmount = inferenceCharge(paymentVoucher, inputTokens, paymentVoucher.maxTokens);
|
|
414
414
|
if (paymentVoucher.maxAmount < requiredMaxAmount) {
|
|
415
|
-
return reply.status(402).send({ error: `Payment voucher maxAmount ${paymentVoucher.maxAmount} is below the estimated ${requiredMaxAmount} micro-
|
|
415
|
+
return reply.status(402).send({ error: `Payment voucher maxAmount ${paymentVoucher.maxAmount} is below the estimated ${requiredMaxAmount} micro-USD`, requiredMaxAmount });
|
|
416
416
|
}
|
|
417
417
|
}
|
|
418
418
|
}
|
|
419
|
+
// Hold the call's price on the payer's balance before running it, so a paid function never runs unpaid.
|
|
420
|
+
const paid = paymentManifest && paymentVoucher ? paymentVoucher : undefined;
|
|
421
|
+
if (paid) {
|
|
422
|
+
const authorization = await options.service.operatorClient.authorize(paid);
|
|
423
|
+
if (!authorization.ok) {
|
|
424
|
+
return reply.status(authorization.status).send({ error: authorization.error });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
419
427
|
const result = await options.service.resourceInvoker.invoke(resource, resourceRequest.functionName, resourceRequest.parameters);
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if (!isInferenceVoucher(
|
|
423
|
-
|
|
428
|
+
if (paid) {
|
|
429
|
+
let capture;
|
|
430
|
+
if (!result.error && !isInferenceVoucher(paid)) {
|
|
431
|
+
capture = { voucher: paid };
|
|
424
432
|
}
|
|
425
|
-
else if (result.usage) {
|
|
433
|
+
else if (!result.error && result.usage && isInferenceVoucher(paid)) {
|
|
426
434
|
// 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,
|
|
435
|
+
const usage = { inputTokens: result.usage.inputTokens, outputTokens: Math.min(result.usage.outputTokens, paid.maxTokens) };
|
|
428
436
|
result.usage = usage;
|
|
429
|
-
// A free call has nothing to
|
|
430
|
-
if (cappedInferenceCharge(
|
|
431
|
-
|
|
437
|
+
// A free call has nothing to charge, so its hold is released instead.
|
|
438
|
+
if (cappedInferenceCharge(paid, usage) > 0) {
|
|
439
|
+
capture = { voucher: paid, usage };
|
|
432
440
|
}
|
|
433
441
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
// Run this on a separate thread to avoid blocking the main thread.
|
|
437
|
-
if (settlement) {
|
|
438
|
-
const payment = settlement;
|
|
442
|
+
// A failed or free call releases its hold. Nothing awaits this, so it doesn't delay the response,
|
|
443
|
+
// and an uncaught error here would be an unhandled rejection.
|
|
439
444
|
setTimeout(async () => {
|
|
440
|
-
// Nothing awaits this callback, so an uncaught error here would be an unhandled rejection.
|
|
441
445
|
try {
|
|
442
|
-
|
|
446
|
+
if (capture) {
|
|
447
|
+
await options.service.operatorClient.capture(capture);
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
await options.service.operatorClient.release(paid);
|
|
451
|
+
}
|
|
443
452
|
}
|
|
444
453
|
catch (error) {
|
|
445
|
-
fastify.log.error(error, 'Failed to
|
|
454
|
+
fastify.log.error(error, capture ? 'Failed to capture payment' : 'Failed to release payment');
|
|
446
455
|
}
|
|
447
456
|
}, 0);
|
|
448
457
|
}
|
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;
|
|
@@ -24,13 +24,18 @@ export interface StellariumNodeOptions {
|
|
|
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,18 @@ 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
|
+
if (hostsPaidFunctions(this.resources) || enrollmentToken) {
|
|
60
|
+
try {
|
|
61
|
+
({ organizationId: config.organizationId } = await new OperatorClient(config.operatorUrl, config.publicKey, config.privateKey).session(enrollmentToken));
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
const hint = enrollmentToken ? '' : '. Create an enrollment token for it at https://platform.stellarium.sh and set ENROLLMENT_TOKEN';
|
|
65
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)}${hint}`, { cause: error });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
55
68
|
const db = await openDatabase(options.databasePath ?? DEFAULT_DATABASE_PATH);
|
|
56
69
|
fastify.addHook('onClose', async () => db.close());
|
|
57
70
|
const service = createService(db, this.resources, config, { approveGrant: options.approveGrant });
|
package/dist/service.d.ts
CHANGED
|
@@ -16,10 +16,15 @@ export interface NodeConfig {
|
|
|
16
16
|
apiKey?: string;
|
|
17
17
|
/** Operator base URL, including `/v1`. Required when the node hosts paid functions. */
|
|
18
18
|
operatorUrl?: string;
|
|
19
|
+
/**
|
|
20
|
+
* The operator organization this node is enrolled in, which is paid for its functions.
|
|
21
|
+
* `StellariumNode.start` asks the operator for it when the node hosts paid functions.
|
|
22
|
+
*/
|
|
23
|
+
organizationId?: string;
|
|
19
24
|
/** Most this node will pay for one remote call. */
|
|
20
|
-
|
|
25
|
+
costLimitMicroUsd: number;
|
|
21
26
|
/** Most this node will sign for in one remote inference call. */
|
|
22
|
-
|
|
27
|
+
inferenceCostLimitMicroUsd: number;
|
|
23
28
|
}
|
|
24
29
|
export interface Service {
|
|
25
30
|
config: NodeConfig;
|
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;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,
|
|
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;;;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,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
|
@@ -6,11 +6,11 @@ import { OperatorClient } from "./client/operatorClient.js";
|
|
|
6
6
|
import { GrantRepository } from "./grant/repository.js";
|
|
7
7
|
import { GrantService } from "./grant/service.js";
|
|
8
8
|
export function createService(db, resources, config, options = {}) {
|
|
9
|
-
const resourceRepository = new ResourceRepository(db, resources, config.baseUrl);
|
|
9
|
+
const resourceRepository = new ResourceRepository(db, resources, config.baseUrl, config.organizationId);
|
|
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.
|
|
13
|
-
const operatorClient = new OperatorClient(config.operatorUrl);
|
|
12
|
+
const resourceInvoker = new CombinationResourceInvoker(resourceRepository, config.costLimitMicroUsd, config.publicKey, config.privateKey, grantService, config.inferenceCostLimitMicroUsd);
|
|
13
|
+
const operatorClient = new OperatorClient(config.operatorUrl, config.publicKey, config.privateKey);
|
|
14
14
|
return {
|
|
15
15
|
config,
|
|
16
16
|
peerRepository: new PeerRepository(db),
|