stellarium-ts 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/operatorAuth.d.ts +19 -0
- package/dist/client/operatorAuth.d.ts.map +1 -0
- package/dist/client/operatorAuth.js +21 -0
- package/dist/client/operatorClient.d.ts +103 -31
- package/dist/client/operatorClient.d.ts.map +1 -1
- package/dist/client/operatorClient.js +107 -23
- package/dist/client/runTokens.d.ts +23 -0
- package/dist/client/runTokens.d.ts.map +1 -0
- package/dist/client/runTokens.js +34 -0
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +8 -5
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/resource/inference.d.ts +5 -3
- package/dist/resource/inference.d.ts.map +1 -1
- package/dist/resource/inference.js +17 -7
- package/dist/resource/invoker.d.ts +48 -14
- package/dist/resource/invoker.d.ts.map +1 -1
- package/dist/resource/invoker.js +108 -38
- package/dist/resource/repository.d.ts +13 -7
- package/dist/resource/repository.d.ts.map +1 -1
- package/dist/resource/repository.js +23 -13
- package/dist/resource/settlement.d.ts +25 -0
- package/dist/resource/settlement.d.ts.map +1 -0
- package/dist/resource/settlement.js +59 -0
- package/dist/routes.d.ts +16 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +103 -76
- package/dist/server.d.ts +11 -6
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +32 -7
- package/dist/service.d.ts +18 -6
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +8 -3
- package/dist/tests/client/operatorClient.test.js +94 -36
- package/dist/tests/db/db.test.js +4 -4
- package/dist/tests/helpers.d.ts +4 -3
- package/dist/tests/helpers.d.ts.map +1 -1
- package/dist/tests/helpers.js +8 -6
- package/dist/tests/integration/network.test.js +69 -18
- package/dist/tests/resource/invoker.test.js +33 -9
- package/dist/tests/resource/repository.test.js +9 -9
- package/dist/tests/routes.test.js +165 -40
- package/dist/tests/server.test.js +15 -15
- package/package.json +3 -2
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** The scheme of the `Authorization` header on node requests to the operator. */
|
|
2
|
+
export declare const OPERATOR_AUTH_SCHEME = "Stellarium";
|
|
3
|
+
/**
|
|
4
|
+
* The object a node signs to authenticate one operator request. `path` is the URL path without the
|
|
5
|
+
* query string, and `bodyHash` is `hashData` of the parsed JSON body (`{}` when there is none).
|
|
6
|
+
* Key order is part of the signed bytes. The operator rebuilds this in apps/operator/src/auth/nodeAuth.ts.
|
|
7
|
+
*/
|
|
8
|
+
export declare function operatorRequestPayload(method: string, path: string, body: unknown, timestamp: string): {
|
|
9
|
+
method: string;
|
|
10
|
+
path: string;
|
|
11
|
+
bodyHash: string;
|
|
12
|
+
timestamp: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* `Authorization` header value for a request to `url`: the scheme, then base64url JSON of
|
|
16
|
+
* `{ publicKey, timestamp, signature }`. The operator accepts timestamps within a few minutes of its clock.
|
|
17
|
+
*/
|
|
18
|
+
export declare function operatorAuthorization(method: string, url: string, body: unknown, publicKey: string, privateKey: string): string;
|
|
19
|
+
//# sourceMappingURL=operatorAuth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operatorAuth.d.ts","sourceRoot":"","sources":["../../src/client/operatorAuth.ts"],"names":[],"mappings":"AAEA,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,eAAe,CAAC;AAEjD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM;;;;;EAEpG;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAK/H"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { hashData, signData } from "../crypto/utils.js";
|
|
2
|
+
/** The scheme of the `Authorization` header on node requests to the operator. */
|
|
3
|
+
export const OPERATOR_AUTH_SCHEME = 'Stellarium';
|
|
4
|
+
/**
|
|
5
|
+
* The object a node signs to authenticate one operator request. `path` is the URL path without the
|
|
6
|
+
* query string, and `bodyHash` is `hashData` of the parsed JSON body (`{}` when there is none).
|
|
7
|
+
* Key order is part of the signed bytes. The operator rebuilds this in apps/operator/src/auth/nodeAuth.ts.
|
|
8
|
+
*/
|
|
9
|
+
export function operatorRequestPayload(method, path, body, timestamp) {
|
|
10
|
+
return { method: method.toUpperCase(), path, bodyHash: hashData(body ?? {}), timestamp };
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* `Authorization` header value for a request to `url`: the scheme, then base64url JSON of
|
|
14
|
+
* `{ publicKey, timestamp, signature }`. The operator accepts timestamps within a few minutes of its clock.
|
|
15
|
+
*/
|
|
16
|
+
export function operatorAuthorization(method, url, body, publicKey, privateKey) {
|
|
17
|
+
const timestamp = new Date().toISOString();
|
|
18
|
+
const signature = signData(operatorRequestPayload(method, new URL(url).pathname, body, timestamp), privateKey);
|
|
19
|
+
const credentials = Buffer.from(JSON.stringify({ publicKey, timestamp, signature })).toString('base64url');
|
|
20
|
+
return `${OPERATOR_AUTH_SCHEME} ${credentials}`;
|
|
21
|
+
}
|
|
@@ -1,48 +1,120 @@
|
|
|
1
1
|
export type PaymentSubmission = FixedPaymentSubmission | InferencePaymentSubmission;
|
|
2
|
+
export interface FixedPaymentVoucher {
|
|
3
|
+
kind?: undefined;
|
|
4
|
+
requestId: string;
|
|
5
|
+
invocationId: string;
|
|
6
|
+
amount: number;
|
|
7
|
+
recipientOrganizationId: string;
|
|
8
|
+
signature: string;
|
|
9
|
+
expiresAt: string;
|
|
10
|
+
invocationHash: string;
|
|
11
|
+
publicKey: string;
|
|
12
|
+
runId?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface InferenceVoucher {
|
|
15
|
+
kind: 'inference';
|
|
16
|
+
requestId: string;
|
|
17
|
+
invocationId: string;
|
|
18
|
+
maxAmount: number;
|
|
19
|
+
inputCostMicroUsdPerMillionTokens: number;
|
|
20
|
+
outputCostMicroUsdPerMillionTokens: number;
|
|
21
|
+
maxTokens: number;
|
|
22
|
+
recipientOrganizationId: string;
|
|
23
|
+
signature: string;
|
|
24
|
+
expiresAt: string;
|
|
25
|
+
invocationHash: string;
|
|
26
|
+
publicKey: string;
|
|
27
|
+
runId?: string;
|
|
28
|
+
}
|
|
2
29
|
export interface FixedPaymentSubmission {
|
|
3
|
-
voucher:
|
|
4
|
-
requestId: string;
|
|
5
|
-
invocationId: string;
|
|
6
|
-
amount: number;
|
|
7
|
-
recipientAddress: string;
|
|
8
|
-
signature: string;
|
|
9
|
-
expiresAt: string;
|
|
10
|
-
invocationHash: string;
|
|
11
|
-
publicKey: string;
|
|
12
|
-
};
|
|
30
|
+
voucher: FixedPaymentVoucher;
|
|
13
31
|
}
|
|
14
|
-
/** The provider reports the call's usage; the operator charges it at the voucher's rates, up to `maxAmount`. */
|
|
32
|
+
/** The provider reports the call's usage; the operator charges it at the voucher's rates, up to the held `maxAmount`. */
|
|
15
33
|
export interface InferencePaymentSubmission {
|
|
16
|
-
voucher:
|
|
17
|
-
kind: 'inference';
|
|
18
|
-
requestId: string;
|
|
19
|
-
invocationId: string;
|
|
20
|
-
maxAmount: number;
|
|
21
|
-
inputCostMicroUsdcPerMillionTokens: number;
|
|
22
|
-
outputCostMicroUsdcPerMillionTokens: number;
|
|
23
|
-
maxTokens: number;
|
|
24
|
-
recipientAddress: string;
|
|
25
|
-
signature: string;
|
|
26
|
-
expiresAt: string;
|
|
27
|
-
invocationHash: string;
|
|
28
|
-
publicKey: string;
|
|
29
|
-
};
|
|
34
|
+
voucher: InferenceVoucher;
|
|
30
35
|
usage: {
|
|
31
36
|
inputTokens: number;
|
|
32
37
|
outputTokens: number;
|
|
33
38
|
};
|
|
34
39
|
}
|
|
35
|
-
|
|
36
|
-
export
|
|
40
|
+
/** Whether the operator placed a hold for a voucher. A refusal carries the operator's status and error. */
|
|
41
|
+
export type AuthorizeResult = {
|
|
42
|
+
ok: true;
|
|
43
|
+
} | {
|
|
44
|
+
ok: false;
|
|
45
|
+
status: number;
|
|
46
|
+
error: string;
|
|
47
|
+
};
|
|
48
|
+
/** What a node sends to authorize a script run billed to an API client. Matches apps/operator/src/runs/runService.ts. */
|
|
49
|
+
export interface RunAuthorizationRequest {
|
|
50
|
+
/** The run token the caller presented. */
|
|
51
|
+
token: string;
|
|
52
|
+
runId: string;
|
|
53
|
+
source: string;
|
|
54
|
+
}
|
|
55
|
+
/** The operator's hold for a run: up to `heldMicroUsd` for the script's tokens and its paid calls together. */
|
|
56
|
+
export type RunAuthorizeResult = {
|
|
57
|
+
ok: true;
|
|
58
|
+
heldMicroUsd: number;
|
|
59
|
+
maxTokens: number;
|
|
60
|
+
} | {
|
|
61
|
+
ok: false;
|
|
62
|
+
status: number;
|
|
63
|
+
error: string;
|
|
64
|
+
};
|
|
65
|
+
export declare const OPERATOR_MAX_RETRIES = 5;
|
|
66
|
+
/** Authorization runs while the caller waits, so it gives up much sooner than capture and release. */
|
|
67
|
+
export declare const AUTHORIZE_MAX_RETRIES = 2;
|
|
68
|
+
export declare const OPERATOR_BASE_DELAY_MS = 1000;
|
|
69
|
+
export declare const AUTHORIZE_BASE_DELAY_MS = 200;
|
|
37
70
|
/** Per attempt. A timed-out attempt is retried like a network error. */
|
|
38
|
-
export declare const
|
|
71
|
+
export declare const OPERATOR_TIMEOUT_MS = 10000;
|
|
72
|
+
/** Talks to the operator as this node. Every request is signed with the node's key. */
|
|
39
73
|
export declare class OperatorClient {
|
|
40
74
|
private readonly url;
|
|
41
|
-
|
|
75
|
+
private readonly publicKey;
|
|
76
|
+
private readonly privateKey;
|
|
77
|
+
constructor(url: string | undefined, publicKey: string, privateKey: string);
|
|
42
78
|
/**
|
|
43
|
-
*
|
|
79
|
+
* POSTs `body`, retrying `RETRYABLE_STATUSES` and network errors up to `retries` times.
|
|
80
|
+
* Each attempt is signed afresh, since the operator only accepts recent timestamps.
|
|
44
81
|
* Throws the last network error if the operator could not be reached, or if no operator URL is configured.
|
|
45
82
|
*/
|
|
46
|
-
|
|
83
|
+
private post;
|
|
84
|
+
/**
|
|
85
|
+
* Returns the organization this node is enrolled in. With an `enrollmentToken` from the dashboard,
|
|
86
|
+
* enrolls the node first; a node that is already enrolled ignores the token. Throws if the node is
|
|
87
|
+
* not enrolled or the operator refuses.
|
|
88
|
+
*/
|
|
89
|
+
session(enrollmentToken?: string, baseUrl?: string): Promise<{
|
|
90
|
+
organizationId: string;
|
|
91
|
+
}>;
|
|
92
|
+
/**
|
|
93
|
+
* Asks the operator to hold the voucher's amount (or `maxAmount`) on the payer's balance, before
|
|
94
|
+
* running the paid function. An unreachable operator is reported as a 503 refusal.
|
|
95
|
+
*/
|
|
96
|
+
authorize(voucher: FixedPaymentVoucher | InferenceVoucher): Promise<AuthorizeResult>;
|
|
97
|
+
/**
|
|
98
|
+
* Asks the operator to hold a script run's cost on the paying API client's organization, before running
|
|
99
|
+
* it. An unreachable operator is reported as a 503 refusal.
|
|
100
|
+
*/
|
|
101
|
+
authorizeRun(request: RunAuthorizationRequest): Promise<RunAuthorizeResult>;
|
|
102
|
+
/** Charges a held run for the tokens its script executed, and releases the rest of its hold. */
|
|
103
|
+
captureRun(request: {
|
|
104
|
+
runId: string;
|
|
105
|
+
tokens: number;
|
|
106
|
+
}): Promise<void>;
|
|
107
|
+
/** Releases a held run without charging it, for a script this node failed to run. */
|
|
108
|
+
releaseRun(request: {
|
|
109
|
+
runId: string;
|
|
110
|
+
}): Promise<void>;
|
|
111
|
+
/** Authorization runs while the caller waits, so it retries briefly and reports an unreachable operator as 503. */
|
|
112
|
+
private hold;
|
|
113
|
+
/** Charges a held voucher: its amount, or for inference the reported usage capped at the hold. */
|
|
114
|
+
capture(payment: PaymentSubmission): Promise<void>;
|
|
115
|
+
/** Releases a held voucher without charging it, for a call that failed or cost nothing. */
|
|
116
|
+
release(voucher: FixedPaymentVoucher | InferenceVoucher): Promise<void>;
|
|
117
|
+
/** Logs a refusal instead of throwing: nothing waits on capture or release. Throws only if the operator can't be reached. */
|
|
118
|
+
private settle;
|
|
47
119
|
}
|
|
48
120
|
//# sourceMappingURL=operatorClient.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operatorClient.d.ts","sourceRoot":"","sources":["../../src/client/operatorClient.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"operatorClient.d.ts","sourceRoot":"","sources":["../../src/client/operatorClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,62 +1,146 @@
|
|
|
1
|
-
|
|
2
|
-
export const
|
|
1
|
+
import { operatorAuthorization } from "./operatorAuth.js";
|
|
2
|
+
export const OPERATOR_MAX_RETRIES = 5;
|
|
3
|
+
/** Authorization runs while the caller waits, so it gives up much sooner than capture and release. */
|
|
4
|
+
export const AUTHORIZE_MAX_RETRIES = 2;
|
|
5
|
+
export const OPERATOR_BASE_DELAY_MS = 1000;
|
|
6
|
+
export const AUTHORIZE_BASE_DELAY_MS = 200;
|
|
3
7
|
/** Per attempt. A timed-out attempt is retried like a network error. */
|
|
4
|
-
export const
|
|
8
|
+
export const OPERATOR_TIMEOUT_MS = 10_000;
|
|
5
9
|
/**
|
|
6
|
-
* Transient failures. Resending is safe even if an earlier attempt reached the operator:
|
|
7
|
-
*
|
|
8
|
-
* Rejections (400, 401, 404, 409, 422) are not retried because they won't change.
|
|
10
|
+
* Transient failures. Resending is safe even if an earlier attempt reached the operator: every payment
|
|
11
|
+
* request is idempotent per voucher (requestId, invocationId).
|
|
12
|
+
* Rejections (400, 401, 402, 403, 404, 409, 422) are not retried because they won't change.
|
|
9
13
|
*/
|
|
10
14
|
const RETRYABLE_STATUSES = new Set([408, 429, 500, 502, 503, 504]);
|
|
11
15
|
/**
|
|
12
|
-
* Delay before retry `attempt` (0-based): exponential backoff
|
|
16
|
+
* Delay before retry `attempt` (0-based): exponential backoff from `baseDelayMs`,
|
|
13
17
|
* or the operator's `Retry-After` seconds when that is longer.
|
|
14
18
|
*/
|
|
15
|
-
function retryDelayMs(response, attempt) {
|
|
16
|
-
const backoff =
|
|
19
|
+
function retryDelayMs(response, attempt, baseDelayMs) {
|
|
20
|
+
const backoff = baseDelayMs * 2 ** attempt;
|
|
17
21
|
const retryAfterSeconds = Number(response?.headers.get('retry-after') ?? NaN);
|
|
18
22
|
return Number.isFinite(retryAfterSeconds) ? Math.max(backoff, retryAfterSeconds * 1000) : backoff;
|
|
19
23
|
}
|
|
24
|
+
async function errorMessage(response) {
|
|
25
|
+
const body = await response.json().catch(() => undefined);
|
|
26
|
+
return typeof body?.error === 'string' ? body.error : `${response.status} ${response.statusText}`.trim();
|
|
27
|
+
}
|
|
28
|
+
/** Talks to the operator as this node. Every request is signed with the node's key. */
|
|
20
29
|
export class OperatorClient {
|
|
21
30
|
url;
|
|
22
|
-
|
|
31
|
+
publicKey;
|
|
32
|
+
privateKey;
|
|
33
|
+
constructor(url, publicKey, privateKey) {
|
|
23
34
|
this.url = url;
|
|
35
|
+
this.publicKey = publicKey;
|
|
36
|
+
this.privateKey = privateKey;
|
|
24
37
|
}
|
|
25
38
|
/**
|
|
26
|
-
*
|
|
39
|
+
* POSTs `body`, retrying `RETRYABLE_STATUSES` and network errors up to `retries` times.
|
|
40
|
+
* Each attempt is signed afresh, since the operator only accepts recent timestamps.
|
|
27
41
|
* Throws the last network error if the operator could not be reached, or if no operator URL is configured.
|
|
28
42
|
*/
|
|
29
|
-
async
|
|
43
|
+
async post(path, body, retries, baseDelayMs) {
|
|
30
44
|
if (!this.url) {
|
|
31
|
-
throw new Error('
|
|
45
|
+
throw new Error('No operator URL is configured');
|
|
32
46
|
}
|
|
33
|
-
const
|
|
34
|
-
|
|
47
|
+
const url = `${this.url}${path}`;
|
|
48
|
+
const serialized = JSON.stringify(body);
|
|
35
49
|
for (let attempt = 0;; attempt++) {
|
|
36
50
|
let retryable;
|
|
37
51
|
try {
|
|
38
|
-
response = await fetch(
|
|
52
|
+
const response = await fetch(url, {
|
|
39
53
|
method: 'POST',
|
|
40
|
-
body,
|
|
54
|
+
body: serialized,
|
|
41
55
|
headers: {
|
|
42
56
|
'Content-Type': 'application/json',
|
|
57
|
+
Authorization: operatorAuthorization('POST', url, body, this.publicKey, this.privateKey),
|
|
43
58
|
},
|
|
44
|
-
signal: AbortSignal.timeout(
|
|
59
|
+
signal: AbortSignal.timeout(OPERATOR_TIMEOUT_MS),
|
|
45
60
|
});
|
|
46
|
-
if (!RETRYABLE_STATUSES.has(response.status) || attempt >=
|
|
47
|
-
|
|
61
|
+
if (!RETRYABLE_STATUSES.has(response.status) || attempt >= retries) {
|
|
62
|
+
return response;
|
|
48
63
|
}
|
|
49
64
|
retryable = response;
|
|
50
65
|
}
|
|
51
66
|
catch (error) {
|
|
52
|
-
if (attempt >=
|
|
67
|
+
if (attempt >= retries) {
|
|
53
68
|
throw error;
|
|
54
69
|
}
|
|
55
70
|
}
|
|
56
|
-
await new Promise(resolve => setTimeout(resolve, retryDelayMs(retryable, attempt)));
|
|
71
|
+
await new Promise(resolve => setTimeout(resolve, retryDelayMs(retryable, attempt, baseDelayMs)));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Returns the organization this node is enrolled in. With an `enrollmentToken` from the dashboard,
|
|
76
|
+
* enrolls the node first; a node that is already enrolled ignores the token. Throws if the node is
|
|
77
|
+
* not enrolled or the operator refuses.
|
|
78
|
+
*/
|
|
79
|
+
async session(enrollmentToken, baseUrl) {
|
|
80
|
+
const body = { ...(enrollmentToken ? { enrollmentToken } : {}), ...(baseUrl ? { baseUrl } : {}) };
|
|
81
|
+
const response = await this.post('/nodes/session', body, OPERATOR_MAX_RETRIES, OPERATOR_BASE_DELAY_MS);
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
throw new Error(`Operator refused this node's session: ${await errorMessage(response)}`);
|
|
57
84
|
}
|
|
85
|
+
return await response.json();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Asks the operator to hold the voucher's amount (or `maxAmount`) on the payer's balance, before
|
|
89
|
+
* running the paid function. An unreachable operator is reported as a 503 refusal.
|
|
90
|
+
*/
|
|
91
|
+
async authorize(voucher) {
|
|
92
|
+
const result = await this.hold('/payments/authorize', { voucher });
|
|
93
|
+
return result.ok ? { ok: true } : result;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Asks the operator to hold a script run's cost on the paying API client's organization, before running
|
|
97
|
+
* it. An unreachable operator is reported as a 503 refusal.
|
|
98
|
+
*/
|
|
99
|
+
async authorizeRun(request) {
|
|
100
|
+
const result = await this.hold('/runs/authorize', request);
|
|
101
|
+
if (!result.ok)
|
|
102
|
+
return result;
|
|
103
|
+
const { heldMicroUsd, maxTokens } = result.body;
|
|
104
|
+
return { ok: true, heldMicroUsd, maxTokens };
|
|
105
|
+
}
|
|
106
|
+
/** Charges a held run for the tokens its script executed, and releases the rest of its hold. */
|
|
107
|
+
async captureRun(request) {
|
|
108
|
+
await this.settle('/runs/capture', request);
|
|
109
|
+
}
|
|
110
|
+
/** Releases a held run without charging it, for a script this node failed to run. */
|
|
111
|
+
async releaseRun(request) {
|
|
112
|
+
await this.settle('/runs/release', request);
|
|
113
|
+
}
|
|
114
|
+
/** Authorization runs while the caller waits, so it retries briefly and reports an unreachable operator as 503. */
|
|
115
|
+
async hold(path, body) {
|
|
116
|
+
let response;
|
|
117
|
+
try {
|
|
118
|
+
response = await this.post(path, body, AUTHORIZE_MAX_RETRIES, AUTHORIZE_BASE_DELAY_MS);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error(`Failed to reach the operator for ${path}`, error);
|
|
122
|
+
return { ok: false, status: 503, error: 'Payment operator is unavailable' };
|
|
123
|
+
}
|
|
124
|
+
if (response.ok) {
|
|
125
|
+
return { ok: true, body: await response.json().catch(() => undefined) };
|
|
126
|
+
}
|
|
127
|
+
// Retries ran out on a transient failure: the caller may try again later.
|
|
128
|
+
const status = RETRYABLE_STATUSES.has(response.status) ? 503 : response.status;
|
|
129
|
+
return { ok: false, status, error: await errorMessage(response) };
|
|
130
|
+
}
|
|
131
|
+
/** Charges a held voucher: its amount, or for inference the reported usage capped at the hold. */
|
|
132
|
+
async capture(payment) {
|
|
133
|
+
await this.settle('/payments/capture', payment);
|
|
134
|
+
}
|
|
135
|
+
/** Releases a held voucher without charging it, for a call that failed or cost nothing. */
|
|
136
|
+
async release(voucher) {
|
|
137
|
+
await this.settle('/payments/release', { voucher });
|
|
138
|
+
}
|
|
139
|
+
/** Logs a refusal instead of throwing: nothing waits on capture or release. Throws only if the operator can't be reached. */
|
|
140
|
+
async settle(path, body) {
|
|
141
|
+
const response = await this.post(path, body, OPERATOR_MAX_RETRIES, OPERATOR_BASE_DELAY_MS);
|
|
58
142
|
if (!response.ok) {
|
|
59
|
-
console.error(`
|
|
143
|
+
console.error(`Operator refused ${path}: ${await errorMessage(response)}`);
|
|
60
144
|
}
|
|
61
145
|
}
|
|
62
146
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type JWTVerifyGetKey } from "jose";
|
|
2
|
+
/** The `iss` of every run token. Copied from RUN_TOKEN_ISSUER in apps/operator/src/runs/tokens.ts. */
|
|
3
|
+
export declare const RUN_TOKEN_ISSUER = "stellarium";
|
|
4
|
+
/** Who a run token lets run scripts on this node, and which organization pays. */
|
|
5
|
+
export interface RunTokenClaims {
|
|
6
|
+
clientId: string;
|
|
7
|
+
organizationId: string;
|
|
8
|
+
}
|
|
9
|
+
/** Whether `token` has the three base64url segments of a JWT, as opposed to an API key. */
|
|
10
|
+
export declare function looksLikeJwt(token: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Verifies the run tokens API clients get from the operator's `POST /v1/oauth/token`, against the
|
|
13
|
+
* operator's published keys. A token is only valid at the node whose public key is its audience.
|
|
14
|
+
*/
|
|
15
|
+
export declare class RunTokenVerifier {
|
|
16
|
+
private readonly nodePublicKey;
|
|
17
|
+
private readonly keys;
|
|
18
|
+
/** `keys` replaces fetching `{operatorUrl}/.well-known/jwks.json`, in tests. */
|
|
19
|
+
constructor(operatorUrl: string, nodePublicKey: string, keys?: JWTVerifyGetKey);
|
|
20
|
+
/** The token's claims, or undefined unless it is signed by the operator, unexpired and addressed to this node. */
|
|
21
|
+
verify(token: string): Promise<RunTokenClaims | undefined>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=runTokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runTokens.d.ts","sourceRoot":"","sources":["../../src/client/runTokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,eAAe,EAAa,MAAM,MAAM,CAAC;AAE3E,sGAAsG;AACtG,eAAO,MAAM,gBAAgB,eAAe,CAAC;AAE7C,kFAAkF;AAClF,MAAM,WAAW,cAAc;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;CAC1B;AAED,2FAA2F;AAC3F,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAIQ,OAAO,CAAC,QAAQ,CAAC,aAAa;IAH/D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAkB;IAEvC,gFAAgF;IAChF,YAAY,WAAW,EAAE,MAAM,EAAmB,aAAa,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,eAAe,EAG9F;IAED,kHAAkH;IAC5G,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAU/D;CACJ"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
2
|
+
/** The `iss` of every run token. Copied from RUN_TOKEN_ISSUER in apps/operator/src/runs/tokens.ts. */
|
|
3
|
+
export const RUN_TOKEN_ISSUER = 'stellarium';
|
|
4
|
+
/** Whether `token` has the three base64url segments of a JWT, as opposed to an API key. */
|
|
5
|
+
export function looksLikeJwt(token) {
|
|
6
|
+
return /^[\w-]+\.[\w-]+\.[\w-]+$/.test(token);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Verifies the run tokens API clients get from the operator's `POST /v1/oauth/token`, against the
|
|
10
|
+
* operator's published keys. A token is only valid at the node whose public key is its audience.
|
|
11
|
+
*/
|
|
12
|
+
export class RunTokenVerifier {
|
|
13
|
+
nodePublicKey;
|
|
14
|
+
keys;
|
|
15
|
+
/** `keys` replaces fetching `{operatorUrl}/.well-known/jwks.json`, in tests. */
|
|
16
|
+
constructor(operatorUrl, nodePublicKey, keys) {
|
|
17
|
+
this.nodePublicKey = nodePublicKey;
|
|
18
|
+
// Cached, and refetched when a token names a key it doesn't have.
|
|
19
|
+
this.keys = keys ?? createRemoteJWKSet(new URL(`${operatorUrl}/.well-known/jwks.json`));
|
|
20
|
+
}
|
|
21
|
+
/** The token's claims, or undefined unless it is signed by the operator, unexpired and addressed to this node. */
|
|
22
|
+
async verify(token) {
|
|
23
|
+
try {
|
|
24
|
+
const { payload } = await jwtVerify(token, this.keys, { issuer: RUN_TOKEN_ISSUER, audience: this.nodePublicKey, algorithms: ['EdDSA'] });
|
|
25
|
+
if (typeof payload.sub !== 'string' || typeof payload.org !== 'string') {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return { clientId: payload.sub, organizationId: payload.org };
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
package/dist/db/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/db/db.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAE7C,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/db/db.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAE7C,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAc1D,wGAAwG;AACxG,wBAAsB,YAAY,CAAC,QAAQ,GAAE,MAA8B,GAAG,OAAO,CAAC,QAAQ,CAAC,CAI9F;AAED,wBAAsB,YAAY,CAAC,EAAE,EAAE,QAAQ,iBA+I9C"}
|
package/dist/db/db.js
CHANGED
|
@@ -8,8 +8,10 @@ export const DEFAULT_DATABASE_PATH = './requestscript.db';
|
|
|
8
8
|
* Version 3 added `resource.website` and the `description` columns of the resource tables.
|
|
9
9
|
* Version 4 renamed `resource_function_payment_manifest.cost_lamports` to `cost_micro_usdc` (payments moved to USDC).
|
|
10
10
|
* Version 5 added `resource_function.kind` and per-token inference pricing, which made `cost_micro_usdc` nullable.
|
|
11
|
+
* Version 6 moved payments to Stripe: `*_micro_usdc` columns became `*_micro_usd`, and `recipient_address`
|
|
12
|
+
* became `recipient_organization_id`.
|
|
11
13
|
*/
|
|
12
|
-
const SCHEMA_VERSION =
|
|
14
|
+
const SCHEMA_VERSION = 6;
|
|
13
15
|
/** Opens (or creates) the node database and brings its schema up to date. Pass ':memory:' for tests. */
|
|
14
16
|
export async function openDatabase(filename = DEFAULT_DATABASE_PATH) {
|
|
15
17
|
const db = await open({ filename, driver: sqlite3.Database });
|
|
@@ -92,10 +94,11 @@ export async function createTables(db) {
|
|
|
92
94
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
93
95
|
resource_function_id INTEGER NOT NULL,
|
|
94
96
|
-- Null for inference functions, which are priced per million tokens instead.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
cost_micro_usd INTEGER,
|
|
98
|
+
input_cost_micro_usd_per_million_tokens INTEGER,
|
|
99
|
+
output_cost_micro_usd_per_million_tokens INTEGER,
|
|
100
|
+
-- The hosting node's organization. Null only if a peer advertised the manifest without one.
|
|
101
|
+
recipient_organization_id TEXT,
|
|
99
102
|
created_at TEXT NOT NULL,
|
|
100
103
|
updated_at TEXT NOT NULL,
|
|
101
104
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export { StellariumNode, resolveConfig, type StellariumNodeOptions } from './server.js';
|
|
2
2
|
export type { NodeConfig } from './service.js';
|
|
3
3
|
export type { StellariumFixedPaymentManifest, StellariumInferencePaymentManifest, StellariumResource, StellariumResourceFunction, StellariumResourceFunctionKind, StellariumResourceFunctionParameter, StellariumResourceFunctionPaymentManifest, } from './resource/repository.js';
|
|
4
|
-
export { CombinationResourceInvoker, inferenceVoucherPayload, invocationHash, paymentVoucherPayload, type InferenceEstimate, type InferencePaymentVoucher, type PaymentVoucher, } from './resource/invoker.js';
|
|
4
|
+
export { CombinationResourceInvoker, inferenceVoucherPayload, invocationHash, paymentVoucherPayload, type RunContext, type InferenceEstimate, type InferencePaymentVoucher, type PaymentVoucher, } from './resource/invoker.js';
|
|
5
5
|
export { inferenceCharge, MAX_TOKENS_PARAMETER, type InferenceResult, type InferenceUsage } from './resource/inference.js';
|
|
6
|
-
export type { InferencePaymentSubmission, PaymentSubmission } from './client/operatorClient.js';
|
|
6
|
+
export type { FixedPaymentVoucher, InferencePaymentSubmission, InferenceVoucher, PaymentSubmission, RunAuthorizationRequest } from './client/operatorClient.js';
|
|
7
|
+
export { RUN_TOKEN_ISSUER, RunTokenVerifier, type RunTokenClaims } from './client/runTokens.js';
|
|
8
|
+
export { OPERATOR_AUTH_SCHEME, operatorAuthorization, operatorRequestPayload } from './client/operatorAuth.js';
|
|
7
9
|
export { grantHash, grantPayload, grantRenewalPayload, grantRequestPayload, grantsHash, CLOCK_SKEW_TOLERANCE_MS, invocationAuthorizationPayload, MAX_AUTHORIZATION_LIFETIME_MS, MAX_GRANT_CHAIN_LENGTH, MAX_GRANT_LIFETIME_MS, type Capability, type Grant, type GrantRenewalRequest, type GrantRequest, type InvocationAuthorization, } from './grant/grant.js';
|
|
8
10
|
export type { ApproveGrant, GrantApproval, GrantOptions } from './grant/service.js';
|
|
9
11
|
export type { GrantKey } from './grant/repository.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACxF,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,YAAY,EACR,8BAA8B,EAC9B,kCAAkC,EAClC,kBAAkB,EAClB,0BAA0B,EAC1B,8BAA8B,EAC9B,mCAAmC,EACnC,yCAAyC,GAC5C,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACH,0BAA0B,EAC1B,uBAAuB,EACvB,cAAc,EACd,qBAAqB,EACrB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,cAAc,GACtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC3H,YAAY,EAAE,0BAA0B,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAEhG,OAAO,EACH,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,UAAU,EACV,uBAAuB,EACvB,8BAA8B,EAC9B,6BAA6B,EAC7B,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,UAAU,EACf,KAAK,KAAK,EACV,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,uBAAuB,GAC/B,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACpF,YAAY,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACxF,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,YAAY,EACR,8BAA8B,EAC9B,kCAAkC,EAClC,kBAAkB,EAClB,0BAA0B,EAC1B,8BAA8B,EAC9B,mCAAmC,EACnC,yCAAyC,GAC5C,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACH,0BAA0B,EAC1B,uBAAuB,EACvB,cAAc,EACd,qBAAqB,EACrB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,cAAc,GACtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC3H,YAAY,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAEhK,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEhG,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAE/G,OAAO,EACH,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,UAAU,EACV,uBAAuB,EACvB,8BAA8B,EAC9B,6BAA6B,EAC7B,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,UAAU,EACf,KAAK,KAAK,EACV,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,uBAAuB,GAC/B,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACpF,YAAY,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,5 +2,9 @@ export { StellariumNode, resolveConfig } from './server.js';
|
|
|
2
2
|
// The voucher wire format, so the operator and other settlers can test against the real signer.
|
|
3
3
|
export { CombinationResourceInvoker, inferenceVoucherPayload, invocationHash, paymentVoucherPayload, } from './resource/invoker.js';
|
|
4
4
|
export { inferenceCharge, MAX_TOKENS_PARAMETER } from './resource/inference.js';
|
|
5
|
+
// Run token verification, so the operator can test its tokens against the real verifier.
|
|
6
|
+
export { RUN_TOKEN_ISSUER, RunTokenVerifier } from './client/runTokens.js';
|
|
7
|
+
// The operator request signature, so the operator can test against the real signer.
|
|
8
|
+
export { OPERATOR_AUTH_SCHEME, operatorAuthorization, operatorRequestPayload } from './client/operatorAuth.js';
|
|
5
9
|
// Grant wire format, so other implementations can test against the real signer.
|
|
6
10
|
export { grantHash, grantPayload, grantRenewalPayload, grantRequestPayload, grantsHash, CLOCK_SKEW_TOLERANCE_MS, invocationAuthorizationPayload, MAX_AUTHORIZATION_LIFETIME_MS, MAX_GRANT_CHAIN_LENGTH, MAX_GRANT_LIFETIME_MS, } from './grant/grant.js';
|
|
@@ -14,19 +14,21 @@ export interface InferenceResult {
|
|
|
14
14
|
}
|
|
15
15
|
/** The per-million-token rates a charge is computed from. */
|
|
16
16
|
export interface InferenceRates {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
inputCostMicroUsdPerMillionTokens: number;
|
|
18
|
+
outputCostMicroUsdPerMillionTokens: number;
|
|
19
19
|
}
|
|
20
20
|
export declare function isInferenceManifest(manifest: StellariumResourceFunctionPaymentManifest | undefined): manifest is StellariumInferencePaymentManifest;
|
|
21
21
|
export declare function isInferenceFunction(fn: Pick<StellariumResourceFunction, 'kind'>): boolean;
|
|
22
22
|
/**
|
|
23
|
-
* Micro-
|
|
23
|
+
* Micro-USD owed for `inputTokens` and `outputTokens` at `rates`, rounded up.
|
|
24
24
|
* Computed exactly in BigInt. Throws if the charge isn't a safe integer.
|
|
25
25
|
* The operator keeps a copy (apps/operator/src/inference.ts) that must stay in sync.
|
|
26
26
|
*/
|
|
27
27
|
export declare function inferenceCharge(rates: InferenceRates, inputTokens: number, outputTokens: number): number;
|
|
28
28
|
/** The call's `maxTokens` value, or undefined unless it is a positive int32. */
|
|
29
29
|
export declare function parseMaxTokens(parameters: ResourceFunctionCallParameter[]): number | undefined;
|
|
30
|
+
/** The function's own input-token estimate, or undefined if its estimator failed or returned something other than a count. */
|
|
31
|
+
export declare function estimateInputTokens(fn: StellariumResourceFunction, parameters: ResourceFunctionCallParameter[]): Promise<number | undefined>;
|
|
30
32
|
/** The usage an inference `exec` reported, or undefined if it isn't two nonnegative safe integers. */
|
|
31
33
|
export declare function parseInferenceResult(result: unknown): InferenceResult | undefined;
|
|
32
34
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inference.d.ts","sourceRoot":"","sources":["../../src/resource/inference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,KAAK,EAAE,kCAAkC,EAAE,0BAA0B,EAAE,yCAAyC,EAAE,MAAM,iBAAiB,CAAC;AAEjJ,8FAA8F;AAC9F,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAIhD,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,sFAAsF;AACtF,MAAM,WAAW,eAAe;IAC5B,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,cAAc,CAAC;CACzB;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC3B,
|
|
1
|
+
{"version":3,"file":"inference.d.ts","sourceRoot":"","sources":["../../src/resource/inference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,KAAK,EAAE,kCAAkC,EAAE,0BAA0B,EAAE,yCAAyC,EAAE,MAAM,iBAAiB,CAAC;AAEjJ,8FAA8F;AAC9F,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAIhD,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,sFAAsF;AACtF,MAAM,WAAW,eAAe;IAC5B,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,cAAc,CAAC;CACzB;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC3B,iCAAiC,EAAE,MAAM,CAAC;IAC1C,kCAAkC,EAAE,MAAM,CAAC;CAC9C;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,yCAAyC,GAAG,SAAS,GAAG,QAAQ,IAAI,kCAAkC,CAEnJ;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,IAAI,CAAC,0BAA0B,EAAE,MAAM,CAAC,GAAG,OAAO,CAEzF;AAMD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAQxG;AAED,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,UAAU,EAAE,6BAA6B,EAAE,GAAG,MAAM,GAAG,SAAS,CAG9F;AAED,8HAA8H;AAC9H,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,0BAA0B,EAAE,UAAU,EAAE,6BAA6B,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAOlJ;AAED,sGAAsG;AACtG,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAajF;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,0BAA0B,GAAG,IAAI,CAsB9E"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export const MAX_TOKENS_PARAMETER = 'maxTokens';
|
|
3
3
|
const INT32_MAX = 2_147_483_647;
|
|
4
4
|
export function isInferenceManifest(manifest) {
|
|
5
|
-
return typeof manifest === 'object' && manifest !== null && '
|
|
5
|
+
return typeof manifest === 'object' && manifest !== null && 'inputCostMicroUsdPerMillionTokens' in manifest;
|
|
6
6
|
}
|
|
7
7
|
export function isInferenceFunction(fn) {
|
|
8
8
|
return fn.kind === 'inference';
|
|
@@ -11,13 +11,13 @@ function isNonnegativeSafeInteger(value) {
|
|
|
11
11
|
return Number.isSafeInteger(value) && value >= 0;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
* Micro-
|
|
14
|
+
* Micro-USD owed for `inputTokens` and `outputTokens` at `rates`, rounded up.
|
|
15
15
|
* Computed exactly in BigInt. Throws if the charge isn't a safe integer.
|
|
16
16
|
* The operator keeps a copy (apps/operator/src/inference.ts) that must stay in sync.
|
|
17
17
|
*/
|
|
18
18
|
export function inferenceCharge(rates, inputTokens, outputTokens) {
|
|
19
|
-
const micros = BigInt(inputTokens) * BigInt(rates.
|
|
20
|
-
+ BigInt(outputTokens) * BigInt(rates.
|
|
19
|
+
const micros = BigInt(inputTokens) * BigInt(rates.inputCostMicroUsdPerMillionTokens)
|
|
20
|
+
+ BigInt(outputTokens) * BigInt(rates.outputCostMicroUsdPerMillionTokens);
|
|
21
21
|
const charge = (micros + 999999n) / 1000000n;
|
|
22
22
|
if (charge > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
23
23
|
throw new Error('Inference charge exceeds the largest safe integer');
|
|
@@ -29,6 +29,16 @@ export function parseMaxTokens(parameters) {
|
|
|
29
29
|
const value = parameters.find(parameter => parameter.name === MAX_TOKENS_PARAMETER)?.value;
|
|
30
30
|
return Number.isInteger(value) && value > 0 && value <= INT32_MAX ? value : undefined;
|
|
31
31
|
}
|
|
32
|
+
/** The function's own input-token estimate, or undefined if its estimator failed or returned something other than a count. */
|
|
33
|
+
export async function estimateInputTokens(fn, parameters) {
|
|
34
|
+
try {
|
|
35
|
+
const inputTokens = await fn.estimateInputTokens?.(parameters);
|
|
36
|
+
return Number.isSafeInteger(inputTokens) && inputTokens >= 0 ? inputTokens : undefined;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
32
42
|
/** The usage an inference `exec` reported, or undefined if it isn't two nonnegative safe integers. */
|
|
33
43
|
export function parseInferenceResult(result) {
|
|
34
44
|
if (typeof result !== 'object' || result === null || !('usage' in result)) {
|
|
@@ -64,9 +74,9 @@ export function validateInferenceFunction(fn) {
|
|
|
64
74
|
return;
|
|
65
75
|
}
|
|
66
76
|
if (!isInferenceManifest(fn.paymentManifest)
|
|
67
|
-
|| !isNonnegativeSafeInteger(fn.paymentManifest.
|
|
68
|
-
|| !isNonnegativeSafeInteger(fn.paymentManifest.
|
|
69
|
-
throw new Error(`Inference function ${fn.name} must price input and output tokens as nonnegative integer micro-
|
|
77
|
+
|| !isNonnegativeSafeInteger(fn.paymentManifest.inputCostMicroUsdPerMillionTokens)
|
|
78
|
+
|| !isNonnegativeSafeInteger(fn.paymentManifest.outputCostMicroUsdPerMillionTokens)) {
|
|
79
|
+
throw new Error(`Inference function ${fn.name} must price input and output tokens as nonnegative integer micro-USD per million tokens`);
|
|
70
80
|
}
|
|
71
81
|
if (typeof fn.estimateInputTokens !== 'function') {
|
|
72
82
|
throw new Error(`Paid inference function ${fn.name} must declare estimateInputTokens`);
|