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.
Files changed (39) hide show
  1. package/dist/client/operatorAuth.d.ts +19 -0
  2. package/dist/client/operatorAuth.d.ts.map +1 -0
  3. package/dist/client/operatorAuth.js +21 -0
  4. package/dist/client/operatorClient.d.ts +68 -31
  5. package/dist/client/operatorClient.d.ts.map +1 -1
  6. package/dist/client/operatorClient.js +82 -23
  7. package/dist/db/db.d.ts.map +1 -1
  8. package/dist/db/db.js +8 -5
  9. package/dist/index.d.ts +2 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +2 -0
  12. package/dist/resource/inference.d.ts +3 -3
  13. package/dist/resource/inference.d.ts.map +1 -1
  14. package/dist/resource/inference.js +7 -7
  15. package/dist/resource/invoker.d.ts +13 -13
  16. package/dist/resource/invoker.d.ts.map +1 -1
  17. package/dist/resource/invoker.js +26 -23
  18. package/dist/resource/repository.d.ts +13 -7
  19. package/dist/resource/repository.d.ts.map +1 -1
  20. package/dist/resource/repository.js +23 -13
  21. package/dist/routes.d.ts.map +1 -1
  22. package/dist/routes.js +46 -37
  23. package/dist/server.d.ts +10 -5
  24. package/dist/server.d.ts.map +1 -1
  25. package/dist/server.js +20 -7
  26. package/dist/service.d.ts +7 -2
  27. package/dist/service.d.ts.map +1 -1
  28. package/dist/service.js +3 -3
  29. package/dist/tests/client/operatorClient.test.js +94 -36
  30. package/dist/tests/db/db.test.js +4 -4
  31. package/dist/tests/helpers.d.ts +4 -3
  32. package/dist/tests/helpers.d.ts.map +1 -1
  33. package/dist/tests/helpers.js +8 -6
  34. package/dist/tests/integration/network.test.js +56 -9
  35. package/dist/tests/resource/invoker.test.js +14 -9
  36. package/dist/tests/resource/repository.test.js +9 -9
  37. package/dist/tests/routes.test.js +71 -38
  38. package/dist/tests/server.test.js +15 -15
  39. package/package.json +1 -1
@@ -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, recipientAddress, expiresAt, invocationHash, publicKey } = voucher;
17
- return { requestId, invocationId, amount, recipientAddress, expiresAt, invocationHash, publicKey };
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, inputCostMicroUsdcPerMillionTokens, outputCostMicroUsdcPerMillionTokens, maxTokens, recipientAddress, expiresAt, invocationHash, publicKey } = voucher;
25
- return { kind, requestId, invocationId, maxAmount, inputCostMicroUsdcPerMillionTokens, outputCostMicroUsdcPerMillionTokens, maxTokens, recipientAddress, expiresAt, invocationHash, publicKey };
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
- costLimitMicroUsdc;
66
+ costLimitMicroUsd;
67
67
  publicKey;
68
68
  privateKey;
69
69
  grantService;
70
- inferenceCostLimitMicroUsdc;
70
+ inferenceCostLimitMicroUsd;
71
71
  /**
72
72
  * Without a `grantService`, calls to restricted remote functions fail.
73
- * `inferenceCostLimitMicroUsdc` caps an inference voucher's `maxAmount`, and defaults to `costLimitMicroUsdc`.
73
+ * `inferenceCostLimitMicroUsd` caps an inference voucher's `maxAmount`, and defaults to `costLimitMicroUsd`.
74
74
  */
75
- constructor(resourceRepository, costLimitMicroUsdc, publicKey, privateKey, grantService, inferenceCostLimitMicroUsdc = costLimitMicroUsdc) {
75
+ constructor(resourceRepository, costLimitMicroUsd, publicKey, privateKey, grantService, inferenceCostLimitMicroUsd = costLimitMicroUsd) {
76
76
  this.resourceRepository = resourceRepository;
77
- this.costLimitMicroUsdc = costLimitMicroUsdc;
77
+ this.costLimitMicroUsd = costLimitMicroUsd;
78
78
  this.publicKey = publicKey;
79
79
  this.privateKey = privateKey;
80
80
  this.grantService = grantService;
81
- this.inferenceCostLimitMicroUsdc = inferenceCostLimitMicroUsdc;
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.inputCostMicroUsdcPerMillionTokens) || manifest.inputCostMicroUsdcPerMillionTokens < 0
124
- || !Number.isSafeInteger(manifest.outputCostMicroUsdcPerMillionTokens) || manifest.outputCostMicroUsdcPerMillionTokens < 0) {
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.inferenceCostLimitMicroUsdc) {
140
- throw new Error(`Function ${functionName} may cost up to ${estimate.maxAmount} micro-USDC, which exceeds the inference cost limit of ${this.inferenceCostLimitMicroUsdc} micro-USDC`);
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.costMicroUsdc > this.costLimitMicroUsdc) {
150
- throw new Error(`Function ${functionName} cost ${manifest.costMicroUsdc} micro-USDC, which exceeds the cost limit of ${this.costLimitMicroUsdc} micro-USDC`);
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.recipientAddress, manifest.costMicroUsdc, resource.path, resource.name, functionName, parameters);
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(recipientAddress, amount, resourcePath, resourceName, functionName, parameters) {
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, recipientAddress, expiresAt, invocationHash: hash, publicKey: this.publicKey }), this.privateKey);
219
- return { requestId, invocationId, amount, recipientAddress, signature, expiresAt, invocationHash: hash, publicKey: this.publicKey };
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
- inputCostMicroUsdcPerMillionTokens: manifest.inputCostMicroUsdcPerMillionTokens,
228
- outputCostMicroUsdcPerMillionTokens: manifest.outputCostMicroUsdcPerMillionTokens,
230
+ inputCostMicroUsdPerMillionTokens: manifest.inputCostMicroUsdPerMillionTokens,
231
+ outputCostMicroUsdPerMillionTokens: manifest.outputCostMicroUsdPerMillionTokens,
229
232
  maxTokens,
230
- recipientAddress: manifest.recipientAddress,
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
- /** A flat price per call. */
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
- costMicroUsdc: number;
8
- recipientAddress: string;
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
- inputCostMicroUsdcPerMillionTokens: number;
14
- outputCostMicroUsdcPerMillionTokens: number;
15
- recipientAddress: string;
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
- constructor(db: Database, resources?: StellariumResource[], baseUrl?: string);
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":"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"}
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
- constructor(db, resources = [], baseUrl = '') {
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, cost_micro_usdc, input_cost_micro_usdc_per_million_tokens, output_cost_micro_usdc_per_million_tokens, recipient_address, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)', [
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.costMicroUsdc,
129
- inference ? manifest.inputCostMicroUsdcPerMillionTokens : null,
130
- inference ? manifest.outputCostMicroUsdcPerMillionTokens : null,
131
- manifest.recipientAddress,
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 => ({ ...r, baseUrl: this.baseUrl }));
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.cost_micro_usdc === null) {
244
+ if (result.cost_micro_usd === null) {
235
245
  return {
236
246
  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,
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
- costMicroUsdc: result.cost_micro_usdc,
245
- recipientAddress: result.recipient_address,
254
+ costMicroUsd: result.cost_micro_usd,
255
+ recipientOrganizationId: result.recipient_organization_id ?? undefined,
246
256
  };
247
257
  }
248
258
  /**
@@ -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;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"}
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', 'recipientAddress', 'signature', 'expiresAt', 'invocationHash', 'publicKey'],
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
- recipientAddress: { type: 'string' },
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', 'inputCostMicroUsdcPerMillionTokens', 'outputCostMicroUsdcPerMillionTokens', 'maxTokens', 'recipientAddress', 'signature', 'expiresAt', 'invocationHash', 'publicKey'],
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
- inputCostMicroUsdcPerMillionTokens: { type: 'integer', minimum: 0 },
86
- outputCostMicroUsdcPerMillionTokens: { type: 'integer', minimum: 0 },
85
+ inputCostMicroUsdPerMillionTokens: { type: 'integer', minimum: 0 },
86
+ outputCostMicroUsdPerMillionTokens: { type: 'integer', minimum: 0 },
87
87
  maxTokens: { type: 'integer', minimum: 1 },
88
- recipientAddress: { type: 'string' },
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 the function's recipient. A fixed voucher must pay at least the function's cost. An inference
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.recipientAddress !== manifest.recipientAddress) {
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.inputCostMicroUsdcPerMillionTokens !== manifest.inputCostMicroUsdcPerMillionTokens
155
- || voucher.outputCostMicroUsdcPerMillionTokens !== manifest.outputCostMicroUsdcPerMillionTokens) {
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.costMicroUsdc) {
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 accrue for `usage`: its charge at the voucher's rates, capped at `maxAmount`. */
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
- inputCostMicroUsdcPerMillionTokens: fn.paymentManifest.inputCostMicroUsdcPerMillionTokens,
259
- outputCostMicroUsdcPerMillionTokens: fn.paymentManifest.outputCostMicroUsdcPerMillionTokens,
260
- recipientAddress: fn.paymentManifest.recipientAddress,
258
+ inputCostMicroUsdPerMillionTokens: fn.paymentManifest.inputCostMicroUsdPerMillionTokens,
259
+ outputCostMicroUsdPerMillionTokens: fn.paymentManifest.outputCostMicroUsdPerMillionTokens,
260
+ recipientOrganizationId: fn.paymentManifest.recipientOrganizationId,
261
261
  } : {
262
- costMicroUsdc: fn.paymentManifest.costMicroUsdc,
263
- recipientAddress: fn.paymentManifest.recipientAddress,
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-USDC`, requiredMaxAmount });
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
- let settlement;
421
- if (paymentManifest && paymentVoucher && !result.error) {
422
- if (!isInferenceVoucher(paymentVoucher)) {
423
- settlement = { voucher: paymentVoucher };
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, paymentVoucher.maxTokens) };
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 settle, and the operator refuses an empty receipt.
430
- if (cappedInferenceCharge(paymentVoucher, usage) > 0) {
431
- settlement = { voucher: paymentVoucher, usage };
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
- // Settle only verified vouchers, and only after a successful invoke. A voucher sent to a free function is ignored.
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
- await options.service.operatorClient.settlePayment(payment);
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 settle payment');
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 DEFAULT_COST_LIMIT_MICRO_USDC = 10000;
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
- /** Most this node pays for one remote call. Defaults to `COST_LIMIT_MICRO_USDC`, then 10,000 ($0.01). */
28
- costLimitMicroUsdc?: number;
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 `INFERENCE_COST_LIMIT_MICRO_USDC`, then to the resolved `costLimitMicroUsdc`.
36
+ * Defaults to `INFERENCE_COST_LIMIT_MICRO_USD`, then to the resolved `costLimitMicroUsd`.
32
37
  */
33
- inferenceCostLimitMicroUsdc?: number;
38
+ inferenceCostLimitMicroUsd?: number;
34
39
  /** SQLite file. Defaults to `./requestscript.db`; use ':memory:' for tests. */
35
40
  databasePath?: string;
36
41
  /**
@@ -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
@@ -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
- export const DEFAULT_COST_LIMIT_MICRO_USDC = 10_000;
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.costLimitMicroUsdc ?? (process.env.COST_LIMIT_MICRO_USDC ? Number(process.env.COST_LIMIT_MICRO_USDC) : DEFAULT_COST_LIMIT_MICRO_USDC);
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('costLimitMicroUsdc must be a nonnegative integer');
22
+ throw new Error('costLimitMicroUsd must be a nonnegative integer');
22
23
  }
23
- const inferenceCostLimit = options.inferenceCostLimitMicroUsdc ?? (process.env.INFERENCE_COST_LIMIT_MICRO_USDC ? Number(process.env.INFERENCE_COST_LIMIT_MICRO_USDC) : costLimit);
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('inferenceCostLimitMicroUsdc must be a nonnegative integer');
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
- costLimitMicroUsdc: costLimit,
40
- inferenceCostLimitMicroUsdc: inferenceCostLimit,
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
- costLimitMicroUsdc: number;
25
+ costLimitMicroUsd: number;
21
26
  /** Most this node will sign for in one remote inference call. */
22
- inferenceCostLimitMicroUsdc: number;
27
+ inferenceCostLimitMicroUsd: number;
23
28
  }
24
29
  export interface Service {
25
30
  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,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"}
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.costLimitMicroUsdc, config.publicKey, config.privateKey, grantService, config.inferenceCostLimitMicroUsdc);
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),