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