stellarium-ts 0.15.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 +75 -16
- 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 +11 -3
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/resource/inference.d.ts +38 -0
- package/dist/resource/inference.d.ts.map +1 -0
- package/dist/resource/inference.js +74 -0
- package/dist/resource/invoker.d.ts +65 -8
- package/dist/resource/invoker.d.ts.map +1 -1
- package/dist/resource/invoker.js +120 -21
- package/dist/resource/repository.d.ts +29 -5
- package/dist/resource/repository.d.ts.map +1 -1
- package/dist/resource/repository.js +38 -7
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +206 -52
- package/dist/server.d.ts +13 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +26 -4
- package/dist/service.d.ts +8 -1
- 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 +18 -1
- package/dist/tests/helpers.d.ts +11 -1
- package/dist/tests/helpers.d.ts.map +1 -1
- package/dist/tests/helpers.js +38 -3
- package/dist/tests/integration/network.test.js +78 -7
- package/dist/tests/resource/invoker.test.js +95 -4
- package/dist/tests/resource/repository.test.js +20 -6
- package/dist/tests/routes.test.js +194 -23
- package/dist/tests/server.test.js +35 -8
- package/package.json +1 -1
package/dist/resource/invoker.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hashData, signData } from "../crypto/utils.js";
|
|
2
|
+
import { inferenceCharge, isInferenceFunction, isInferenceManifest, parseInferenceResult, parseMaxTokens } from "./inference.js";
|
|
2
3
|
/**
|
|
3
4
|
* Hash binding a voucher to one function call. Providers recompute it from the
|
|
4
5
|
* request they receive and compare it to `voucher.invocationHash`.
|
|
@@ -12,23 +13,39 @@ export function invocationHash(resourcePath, resourceName, functionName, paramet
|
|
|
12
13
|
* Key order is part of the signed bytes.
|
|
13
14
|
*/
|
|
14
15
|
export function paymentVoucherPayload(voucher) {
|
|
15
|
-
const { requestId, invocationId, amount,
|
|
16
|
-
return { requestId, invocationId, amount,
|
|
16
|
+
const { requestId, invocationId, amount, recipientOrganizationId, expiresAt, invocationHash, publicKey } = voucher;
|
|
17
|
+
return { requestId, invocationId, amount, recipientOrganizationId, expiresAt, invocationHash, publicKey };
|
|
17
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* The object `createInferenceVoucher` signs: every inference voucher field except `signature`.
|
|
21
|
+
* Key order is part of the signed bytes.
|
|
22
|
+
*/
|
|
23
|
+
export function inferenceVoucherPayload(voucher) {
|
|
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
|
+
}
|
|
27
|
+
export function isInferenceVoucher(voucher) {
|
|
28
|
+
return voucher.kind === 'inference';
|
|
29
|
+
}
|
|
30
|
+
export const PAYMENT_VOUCHER_LIFETIME_MS = 1000 * 60 * 2;
|
|
31
|
+
/** Inference can run for minutes, and the operator refuses a voucher that expired before the provider settled it. */
|
|
32
|
+
export const INFERENCE_VOUCHER_LIFETIME_MS = 1000 * 60 * 10;
|
|
18
33
|
export const EXTERNAL_INVOKE_MAX_RETRIES = 3;
|
|
19
34
|
export const EXTERNAL_INVOKE_BASE_DELAY_MS = 500;
|
|
20
35
|
/** Per attempt. A timed-out attempt is retried like a network error. */
|
|
21
36
|
export const EXTERNAL_INVOKE_TIMEOUT_MS = 30_000;
|
|
37
|
+
/** Per attempt, for calls to inference functions. Every attempt fits within `INFERENCE_VOUCHER_LIFETIME_MS`. */
|
|
38
|
+
export const INFERENCE_INVOKE_TIMEOUT_MS = 120_000;
|
|
22
39
|
/**
|
|
23
40
|
* POST to a remote resource, retrying network errors, 429s, and 5xx responses up to
|
|
24
41
|
* `EXTERNAL_INVOKE_MAX_RETRIES` times with exponential backoff (500ms, 1s, 2s).
|
|
25
42
|
* Other 4xx responses are returned immediately since retrying won't change them.
|
|
26
43
|
* Throws the last network error if every attempt fails to connect.
|
|
27
44
|
*/
|
|
28
|
-
async function fetchWithRetry(url, init) {
|
|
45
|
+
async function fetchWithRetry(url, init, timeoutMs = EXTERNAL_INVOKE_TIMEOUT_MS) {
|
|
29
46
|
for (let attempt = 0;; attempt++) {
|
|
30
47
|
try {
|
|
31
|
-
const response = await fetch(url, { ...init(), signal: AbortSignal.timeout(
|
|
48
|
+
const response = await fetch(url, { ...init(), signal: AbortSignal.timeout(timeoutMs) });
|
|
32
49
|
if (response.status !== 429 && response.status < 500) {
|
|
33
50
|
return response;
|
|
34
51
|
}
|
|
@@ -46,24 +63,30 @@ async function fetchWithRetry(url, init) {
|
|
|
46
63
|
}
|
|
47
64
|
export class CombinationResourceInvoker {
|
|
48
65
|
resourceRepository;
|
|
49
|
-
|
|
66
|
+
costLimitMicroUsd;
|
|
50
67
|
publicKey;
|
|
51
68
|
privateKey;
|
|
52
69
|
grantService;
|
|
53
|
-
|
|
54
|
-
|
|
70
|
+
inferenceCostLimitMicroUsd;
|
|
71
|
+
/**
|
|
72
|
+
* Without a `grantService`, calls to restricted remote functions fail.
|
|
73
|
+
* `inferenceCostLimitMicroUsd` caps an inference voucher's `maxAmount`, and defaults to `costLimitMicroUsd`.
|
|
74
|
+
*/
|
|
75
|
+
constructor(resourceRepository, costLimitMicroUsd, publicKey, privateKey, grantService, inferenceCostLimitMicroUsd = costLimitMicroUsd) {
|
|
55
76
|
this.resourceRepository = resourceRepository;
|
|
56
|
-
this.
|
|
77
|
+
this.costLimitMicroUsd = costLimitMicroUsd;
|
|
57
78
|
this.publicKey = publicKey;
|
|
58
79
|
this.privateKey = privateKey;
|
|
59
80
|
this.grantService = grantService;
|
|
81
|
+
this.inferenceCostLimitMicroUsd = inferenceCostLimitMicroUsd;
|
|
60
82
|
}
|
|
61
83
|
/**
|
|
62
84
|
* Invoke a function on a resource.
|
|
63
85
|
* If the resource has a baseUrl, it will be invoked as a remote function.
|
|
64
86
|
* If the function is restricted, it attaches an authorization from a held grant.
|
|
65
87
|
* If the resource has a paymentManifest, it will create a payment voucher and pass it to the resource.
|
|
66
|
-
*
|
|
88
|
+
* A paid inference function is first priced by the provider's estimate endpoint.
|
|
89
|
+
* Otherwise, it will be invoked as a local function. A local inference function's usage is returned beside its value.
|
|
67
90
|
*
|
|
68
91
|
* @param resource - The resource to invoke
|
|
69
92
|
* @param functionName - The name of the function to invoke
|
|
@@ -92,14 +115,44 @@ export class CombinationResourceInvoker {
|
|
|
92
115
|
if (resourceFunction.restricted && !chain) {
|
|
93
116
|
return { value: null, error: { message: `Failed to invoke ${resource.name}.${functionName}: it requires a grant from ${url}, and this node holds none that covers the call` } };
|
|
94
117
|
}
|
|
95
|
-
|
|
96
|
-
|
|
118
|
+
const manifest = resourceFunction.paymentManifest;
|
|
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
|
+
}
|
|
123
|
+
if (manifest && inference) {
|
|
124
|
+
console.log(`Resource function ${resource.name}.${functionName} is a paid inference function, creating payment voucher...`);
|
|
125
|
+
// Peers' manifests aren't validated on receipt.
|
|
126
|
+
if (!isInferenceManifest(manifest) || !Number.isSafeInteger(manifest.inputCostMicroUsdPerMillionTokens) || manifest.inputCostMicroUsdPerMillionTokens < 0
|
|
127
|
+
|| !Number.isSafeInteger(manifest.outputCostMicroUsdPerMillionTokens) || manifest.outputCostMicroUsdPerMillionTokens < 0) {
|
|
128
|
+
return { value: null, error: { message: `Failed to invoke ${resource.name}.${functionName}: its payment manifest has no valid per-token pricing` } };
|
|
129
|
+
}
|
|
130
|
+
const maxTokens = parseMaxTokens(parameters);
|
|
131
|
+
if (maxTokens === undefined) {
|
|
132
|
+
return { value: null, error: { message: `Failed to invoke ${resource.name}.${functionName}: maxTokens must be a positive int32` } };
|
|
133
|
+
}
|
|
134
|
+
const estimate = await this.estimate(url, target, chain);
|
|
135
|
+
if ('error' in estimate) {
|
|
136
|
+
return { value: null, error: { message: `Failed to invoke ${resource.name}.${functionName}: ${estimate.error}` } };
|
|
137
|
+
}
|
|
138
|
+
// The provider may pick the input estimate, but maxAmount must follow from it at the advertised rates.
|
|
139
|
+
if (inferenceCharge(manifest, estimate.inputTokens, maxTokens) !== estimate.maxAmount) {
|
|
140
|
+
return { value: null, error: { message: `Failed to invoke ${resource.name}.${functionName}: the provider's estimate doesn't match its advertised rates` } };
|
|
141
|
+
}
|
|
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`);
|
|
144
|
+
}
|
|
145
|
+
requestBody.paymentVoucher = this.createInferenceVoucher(manifest, estimate.maxAmount, maxTokens, resource.path, resource.name, functionName, parameters);
|
|
146
|
+
}
|
|
147
|
+
else if (manifest) {
|
|
97
148
|
console.log(`Resource function ${resource.name}.${functionName} has a payment manifest, creating payment voucher...`);
|
|
98
|
-
if (
|
|
99
|
-
|
|
149
|
+
if (isInferenceManifest(manifest)) {
|
|
150
|
+
return { value: null, error: { message: `Failed to invoke ${resource.name}.${functionName}: it has per-token pricing but isn't an inference function` } };
|
|
151
|
+
}
|
|
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`);
|
|
100
154
|
}
|
|
101
|
-
paymentVoucher = this.createPaymentVoucher(
|
|
102
|
-
requestBody.paymentVoucher = paymentVoucher;
|
|
155
|
+
requestBody.paymentVoucher = this.createPaymentVoucher(manifest.recipientOrganizationId, manifest.costMicroUsd, resource.path, resource.name, functionName, parameters);
|
|
103
156
|
}
|
|
104
157
|
// The voucher is created once and reused on every attempt, so the operator's
|
|
105
158
|
// requestId/invocationId check prevents a retried call from being settled twice.
|
|
@@ -110,7 +163,7 @@ export class CombinationResourceInvoker {
|
|
|
110
163
|
headers: {
|
|
111
164
|
'Content-Type': 'application/json',
|
|
112
165
|
},
|
|
113
|
-
}));
|
|
166
|
+
}), inference ? INFERENCE_INVOKE_TIMEOUT_MS : EXTERNAL_INVOKE_TIMEOUT_MS);
|
|
114
167
|
if (!result.ok) {
|
|
115
168
|
// The provider's JSON error is more useful than the status text, which can be empty.
|
|
116
169
|
const detail = await result.json().then((body) => body?.error, () => undefined);
|
|
@@ -126,16 +179,62 @@ export class CombinationResourceInvoker {
|
|
|
126
179
|
return { value: null, error: { message: `Function ${functionName} not found in resource ${resource.name}` } };
|
|
127
180
|
}
|
|
128
181
|
const result = await fn.exec(parameters);
|
|
182
|
+
if (isInferenceFunction(fn)) {
|
|
183
|
+
const inferenceResult = parseInferenceResult(result);
|
|
184
|
+
if (!inferenceResult) {
|
|
185
|
+
return { value: null, error: { message: `Inference function ${functionName} must return { value, usage: { inputTokens, outputTokens } }` } };
|
|
186
|
+
}
|
|
187
|
+
return { value: inferenceResult.value, error: null, usage: inferenceResult.usage };
|
|
188
|
+
}
|
|
129
189
|
return { value: result, error: null };
|
|
130
190
|
}
|
|
131
191
|
}
|
|
132
|
-
|
|
192
|
+
/** Asks the provider what a call to a paid inference function will cost at most. */
|
|
193
|
+
async estimate(url, target, chain) {
|
|
194
|
+
let response;
|
|
195
|
+
try {
|
|
196
|
+
// The authorization is signed per attempt, since the provider refuses a reused nonce.
|
|
197
|
+
response = await fetchWithRetry(`${url}/v1/run/resource/estimate`, () => ({
|
|
198
|
+
method: 'POST',
|
|
199
|
+
body: JSON.stringify(chain && this.grantService ? { ...target, authorization: this.grantService.authorize(chain, target) } : target),
|
|
200
|
+
headers: { 'Content-Type': 'application/json' },
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
return { error: `estimate failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
205
|
+
}
|
|
206
|
+
const body = await response.json().catch(() => undefined);
|
|
207
|
+
if (!response.ok) {
|
|
208
|
+
return { error: `estimate failed: ${typeof body?.error === 'string' ? body.error : `${response.status} ${response.statusText}`.trim()}` };
|
|
209
|
+
}
|
|
210
|
+
const { inputTokens, maxAmount } = body ?? {};
|
|
211
|
+
if (!Number.isSafeInteger(inputTokens) || inputTokens < 0 || !Number.isSafeInteger(maxAmount) || maxAmount < 0) {
|
|
212
|
+
return { error: 'the provider returned an invalid estimate' };
|
|
213
|
+
}
|
|
214
|
+
return { inputTokens: inputTokens, maxAmount: maxAmount };
|
|
215
|
+
}
|
|
216
|
+
createPaymentVoucher(recipientOrganizationId, amount, resourcePath, resourceName, functionName, parameters) {
|
|
133
217
|
const requestId = crypto.randomUUID();
|
|
134
218
|
const invocationId = crypto.randomUUID();
|
|
135
|
-
|
|
136
|
-
const expiresAt = new Date(Date.now() + 1000 * 60 * 2).toISOString();
|
|
219
|
+
const expiresAt = new Date(Date.now() + PAYMENT_VOUCHER_LIFETIME_MS).toISOString();
|
|
137
220
|
const hash = invocationHash(resourcePath, resourceName, functionName, parameters);
|
|
138
|
-
const signature = signData(paymentVoucherPayload({ requestId, invocationId, amount,
|
|
139
|
-
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 };
|
|
223
|
+
}
|
|
224
|
+
createInferenceVoucher(manifest, maxAmount, maxTokens, resourcePath, resourceName, functionName, parameters) {
|
|
225
|
+
const unsigned = {
|
|
226
|
+
kind: 'inference',
|
|
227
|
+
requestId: crypto.randomUUID(),
|
|
228
|
+
invocationId: crypto.randomUUID(),
|
|
229
|
+
maxAmount,
|
|
230
|
+
inputCostMicroUsdPerMillionTokens: manifest.inputCostMicroUsdPerMillionTokens,
|
|
231
|
+
outputCostMicroUsdPerMillionTokens: manifest.outputCostMicroUsdPerMillionTokens,
|
|
232
|
+
maxTokens,
|
|
233
|
+
recipientOrganizationId: manifest.recipientOrganizationId,
|
|
234
|
+
expiresAt: new Date(Date.now() + INFERENCE_VOUCHER_LIFETIME_MS).toISOString(),
|
|
235
|
+
invocationHash: invocationHash(resourcePath, resourceName, functionName, parameters),
|
|
236
|
+
publicKey: this.publicKey,
|
|
237
|
+
};
|
|
238
|
+
return { ...unsigned, signature: signData(inferenceVoucherPayload(unsigned), this.privateKey) };
|
|
140
239
|
}
|
|
141
240
|
}
|
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
import { Resource, ResourceFunction, ResourceFunctionParameter } from "requestscript";
|
|
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
|
+
*/
|
|
8
|
+
export interface StellariumFixedPaymentManifest {
|
|
9
|
+
id?: number;
|
|
10
|
+
costMicroUsd: number;
|
|
11
|
+
recipientOrganizationId?: string;
|
|
12
|
+
}
|
|
13
|
+
/** Per-token pricing for an inference function. Either rate may be 0. */
|
|
14
|
+
export interface StellariumInferencePaymentManifest {
|
|
5
15
|
id?: number;
|
|
6
|
-
|
|
7
|
-
|
|
16
|
+
inputCostMicroUsdPerMillionTokens: number;
|
|
17
|
+
outputCostMicroUsdPerMillionTokens: number;
|
|
18
|
+
recipientOrganizationId?: string;
|
|
8
19
|
}
|
|
20
|
+
export type StellariumResourceFunctionPaymentManifest = StellariumFixedPaymentManifest | StellariumInferencePaymentManifest;
|
|
21
|
+
/** Fixed functions return one value. Inference functions run AI inference and are priced per token. */
|
|
22
|
+
export type StellariumResourceFunctionKind = 'fixed' | 'inference';
|
|
9
23
|
export interface StellariumResourceFunctionParameter extends ResourceFunctionParameter {
|
|
10
24
|
description?: string;
|
|
11
25
|
}
|
|
@@ -16,6 +30,13 @@ export interface StellariumResourceFunction extends ResourceFunction {
|
|
|
16
30
|
paymentManifest?: StellariumResourceFunctionPaymentManifest;
|
|
17
31
|
/** Callers need a grant from the hosting node. Announced to peers so callers can tell. */
|
|
18
32
|
restricted?: boolean;
|
|
33
|
+
/** Absent means 'fixed'. An inference function takes a `maxTokens: int32` parameter and its `exec` returns `{ value, usage }`. */
|
|
34
|
+
kind?: StellariumResourceFunctionKind;
|
|
35
|
+
/**
|
|
36
|
+
* Input tokens a call will use, for pricing its voucher before inference runs. Required on paid inference
|
|
37
|
+
* functions. It runs only on the hosting node and, like `exec`, is never announced.
|
|
38
|
+
*/
|
|
39
|
+
estimateInputTokens?: (args: ResourceFunctionCallParameter[]) => Promise<number>;
|
|
19
40
|
}
|
|
20
41
|
export interface StellariumResource extends Resource {
|
|
21
42
|
id: number;
|
|
@@ -43,8 +64,10 @@ export declare class ResourceRepository {
|
|
|
43
64
|
private readonly db;
|
|
44
65
|
private resources;
|
|
45
66
|
private readonly baseUrl;
|
|
67
|
+
private readonly organizationId?;
|
|
46
68
|
private writes;
|
|
47
|
-
|
|
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);
|
|
48
71
|
private serialize;
|
|
49
72
|
private transaction;
|
|
50
73
|
private isInternal;
|
|
@@ -66,6 +89,7 @@ export declare class ResourceRepository {
|
|
|
66
89
|
private deleteFunctions;
|
|
67
90
|
getInternalResource(path: string, name: string): Promise<StellariumResource | undefined>;
|
|
68
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. */
|
|
69
93
|
getInternalResources(): Promise<StellariumResource[]>;
|
|
70
94
|
/** Learned resources in insertion order. A negative `limit` means no limit. */
|
|
71
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,yBAAyB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/resource/repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isFreshExpiry, safeVerifyData } from "../crypto/utils.js";
|
|
2
|
+
import { isInferenceManifest } from "./inference.js";
|
|
2
3
|
/**
|
|
3
4
|
* The object a node signs to broadcast its resources. Key order is part of the signed bytes.
|
|
4
5
|
* Functions' `exec` implementations are dropped by JSON serialization on both ends.
|
|
@@ -23,12 +24,15 @@ export class ResourceRepository {
|
|
|
23
24
|
db;
|
|
24
25
|
resources;
|
|
25
26
|
baseUrl;
|
|
27
|
+
organizationId;
|
|
26
28
|
// The connection is shared, so transactions are serialized to keep them from nesting.
|
|
27
29
|
writes = Promise.resolve();
|
|
28
|
-
|
|
30
|
+
/** `organizationId` is the operator organization this node is enrolled in, which is paid for its functions. */
|
|
31
|
+
constructor(db, resources = [], baseUrl = '', organizationId) {
|
|
29
32
|
this.db = db;
|
|
30
33
|
this.resources = resources;
|
|
31
34
|
this.baseUrl = baseUrl;
|
|
35
|
+
this.organizationId = organizationId;
|
|
32
36
|
}
|
|
33
37
|
serialize(work) {
|
|
34
38
|
const run = this.writes.then(work, work);
|
|
@@ -114,13 +118,23 @@ export class ResourceRepository {
|
|
|
114
118
|
async insertFunctions(resourceId, functions) {
|
|
115
119
|
const createdAt = new Date().toISOString();
|
|
116
120
|
for (const fn of functions) {
|
|
117
|
-
const functionResult = await this.db.run('INSERT INTO resource_function (resource_id, name, return_type, description, restricted, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)', [resourceId, fn.name, fn.returnType, optionalText(fn.description), fn.restricted === true ? 1 : 0, createdAt, createdAt]);
|
|
121
|
+
const functionResult = await this.db.run('INSERT INTO resource_function (resource_id, name, return_type, description, restricted, kind, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [resourceId, fn.name, fn.returnType, optionalText(fn.description), fn.restricted === true ? 1 : 0, fn.kind === 'inference' ? 'inference' : 'fixed', createdAt, createdAt]);
|
|
118
122
|
const functionId = functionResult.lastID;
|
|
119
123
|
for (const parameter of fn.parameters) {
|
|
120
124
|
await this.db.run('INSERT INTO resource_function_parameter (resource_function_id, name, parameter_type, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)', [functionId, parameter.name, parameter.type, optionalText(parameter.description), createdAt, createdAt]);
|
|
121
125
|
}
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
const manifest = fn.paymentManifest;
|
|
127
|
+
if (manifest) {
|
|
128
|
+
const inference = isInferenceManifest(manifest);
|
|
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 (?, ?, ?, ?, ?, ?, ?)', [
|
|
130
|
+
functionId,
|
|
131
|
+
inference ? null : manifest.costMicroUsd,
|
|
132
|
+
inference ? manifest.inputCostMicroUsdPerMillionTokens : null,
|
|
133
|
+
inference ? manifest.outputCostMicroUsdPerMillionTokens : null,
|
|
134
|
+
manifest.recipientOrganizationId ?? null,
|
|
135
|
+
createdAt,
|
|
136
|
+
createdAt,
|
|
137
|
+
]);
|
|
124
138
|
}
|
|
125
139
|
}
|
|
126
140
|
}
|
|
@@ -156,8 +170,15 @@ export class ResourceRepository {
|
|
|
156
170
|
}
|
|
157
171
|
};
|
|
158
172
|
}
|
|
173
|
+
/** This node's resources, with its base URL and its organization as the recipient of every paid function. */
|
|
159
174
|
async getInternalResources() {
|
|
160
|
-
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
|
+
}));
|
|
161
182
|
}
|
|
162
183
|
/** Learned resources in insertion order. A negative `limit` means no limit. */
|
|
163
184
|
async getExternalResources(offset = 0, limit = -1) {
|
|
@@ -191,6 +212,7 @@ export class ResourceRepository {
|
|
|
191
212
|
exec: async () => undefined,
|
|
192
213
|
paymentManifest: await this.getResourceFunctionPaymentManifest(fn.id),
|
|
193
214
|
restricted: fn.restricted === 1 || undefined,
|
|
215
|
+
kind: fn.kind === 'inference' ? 'inference' : undefined,
|
|
194
216
|
};
|
|
195
217
|
}
|
|
196
218
|
async getResourceFunctions(resourceId) {
|
|
@@ -204,6 +226,7 @@ export class ResourceRepository {
|
|
|
204
226
|
exec: async () => undefined,
|
|
205
227
|
paymentManifest: await this.getResourceFunctionPaymentManifest(f.id),
|
|
206
228
|
restricted: f.restricted === 1 || undefined,
|
|
229
|
+
kind: f.kind === 'inference' ? 'inference' : undefined,
|
|
207
230
|
})));
|
|
208
231
|
}
|
|
209
232
|
async getResourceParameters(resourceFunctionId) {
|
|
@@ -218,10 +241,18 @@ export class ResourceRepository {
|
|
|
218
241
|
if (!result) {
|
|
219
242
|
return undefined;
|
|
220
243
|
}
|
|
244
|
+
if (result.cost_micro_usd === null) {
|
|
245
|
+
return {
|
|
246
|
+
id: result.id,
|
|
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,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
221
252
|
return {
|
|
222
253
|
id: result.id,
|
|
223
|
-
|
|
224
|
-
|
|
254
|
+
costMicroUsd: result.cost_micro_usd,
|
|
255
|
+
recipientOrganizationId: result.recipient_organization_id ?? undefined,
|
|
225
256
|
};
|
|
226
257
|
}
|
|
227
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;
|
|
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"}
|