stellarium-ts 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/operatorAuth.d.ts +19 -0
- package/dist/client/operatorAuth.d.ts.map +1 -0
- package/dist/client/operatorAuth.js +21 -0
- package/dist/client/operatorClient.d.ts +68 -31
- package/dist/client/operatorClient.d.ts.map +1 -1
- package/dist/client/operatorClient.js +82 -23
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +8 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/resource/inference.d.ts +3 -3
- package/dist/resource/inference.d.ts.map +1 -1
- package/dist/resource/inference.js +7 -7
- package/dist/resource/invoker.d.ts +13 -13
- package/dist/resource/invoker.d.ts.map +1 -1
- package/dist/resource/invoker.js +26 -23
- package/dist/resource/repository.d.ts +13 -7
- package/dist/resource/repository.d.ts.map +1 -1
- package/dist/resource/repository.js +23 -13
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +46 -37
- package/dist/server.d.ts +10 -5
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +20 -7
- package/dist/service.d.ts +7 -2
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +3 -3
- package/dist/tests/client/operatorClient.test.js +94 -36
- package/dist/tests/db/db.test.js +4 -4
- package/dist/tests/helpers.d.ts +4 -3
- package/dist/tests/helpers.d.ts.map +1 -1
- package/dist/tests/helpers.js +8 -6
- package/dist/tests/integration/network.test.js +56 -9
- package/dist/tests/resource/invoker.test.js +14 -9
- package/dist/tests/resource/repository.test.js +9 -9
- package/dist/tests/routes.test.js +71 -38
- package/dist/tests/server.test.js +15 -15
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@ import { CombinationResourceInvoker, inferenceVoucherPayload, paymentVoucherPayl
|
|
|
10
10
|
import { resourceAnnouncementPayload } from "../resource/repository.js";
|
|
11
11
|
import routes from "../routes.js";
|
|
12
12
|
import { createService } from "../service.js";
|
|
13
|
-
import { COST, generateBase64KeyPair, INFERENCE_RATES, modelResource,
|
|
13
|
+
import { COST, generateBase64KeyPair, INFERENCE_RATES, modelResource, ORGANIZATION, temperatureSource, testConfig, weatherResource } from "./helpers.js";
|
|
14
14
|
const parameters = [{ name: "city", value: "Oslo" }];
|
|
15
15
|
const inMinutes = (minutes) => new Date(Date.now() + minutes * 60_000).toISOString();
|
|
16
16
|
describe("routes", () => {
|
|
@@ -22,12 +22,16 @@ describe("routes", () => {
|
|
|
22
22
|
let config;
|
|
23
23
|
let service;
|
|
24
24
|
let fastify;
|
|
25
|
-
let
|
|
25
|
+
let authorize;
|
|
26
|
+
let capture;
|
|
27
|
+
let release;
|
|
26
28
|
beforeEach(async () => {
|
|
27
29
|
db = await openDatabase(":memory:");
|
|
28
30
|
config = testConfig();
|
|
29
31
|
service = createService(db, [weather], config);
|
|
30
|
-
|
|
32
|
+
authorize = vi.spyOn(service.operatorClient, "authorize").mockResolvedValue({ ok: true });
|
|
33
|
+
capture = vi.spyOn(service.operatorClient, "capture").mockResolvedValue(undefined);
|
|
34
|
+
release = vi.spyOn(service.operatorClient, "release").mockResolvedValue(undefined);
|
|
31
35
|
fastify = Fastify();
|
|
32
36
|
await fastify.register(routes, { prefix: "/v1", service });
|
|
33
37
|
await fastify.ready();
|
|
@@ -41,7 +45,7 @@ describe("routes", () => {
|
|
|
41
45
|
const flushSettlement = () => new Promise((resolve) => setTimeout(resolve, 10));
|
|
42
46
|
describe("POST /v1/run/resource", () => {
|
|
43
47
|
function voucherFor(functionName = "temperature", overrides = {}) {
|
|
44
|
-
const voucher = callerInvoker.createPaymentVoucher(
|
|
48
|
+
const voucher = callerInvoker.createPaymentVoucher(ORGANIZATION, COST, weather.path, weather.name, functionName, parameters);
|
|
45
49
|
if (Object.keys(overrides).length === 0) {
|
|
46
50
|
return voucher;
|
|
47
51
|
}
|
|
@@ -56,13 +60,30 @@ describe("routes", () => {
|
|
|
56
60
|
payload: { resourcePath: weather.path, resourceName: weather.name, functionName: "temperature", parameters, ...body },
|
|
57
61
|
});
|
|
58
62
|
}
|
|
59
|
-
test("
|
|
63
|
+
test("authorizes a paid function's voucher, runs it, then captures it", async () => {
|
|
60
64
|
const paymentVoucher = voucherFor();
|
|
61
65
|
const response = await runResource({ paymentVoucher });
|
|
62
66
|
expect(response.statusCode).toBe(200);
|
|
63
67
|
expect(response.json()).toEqual({ value: 12, error: null });
|
|
68
|
+
expect(authorize).toHaveBeenCalledExactlyOnceWith(paymentVoucher);
|
|
64
69
|
await flushSettlement();
|
|
65
|
-
expect(
|
|
70
|
+
expect(capture).toHaveBeenCalledExactlyOnceWith({ voucher: paymentVoucher });
|
|
71
|
+
expect(release).not.toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
test.each([
|
|
74
|
+
[402, "Insufficient balance"],
|
|
75
|
+
[403, "Payer organization is frozen"],
|
|
76
|
+
[503, "Payment operator is unavailable"],
|
|
77
|
+
])("answers the operator's %i refusal without running the function", async (status, error) => {
|
|
78
|
+
authorize.mockResolvedValueOnce({ ok: false, status, error });
|
|
79
|
+
const exec = vi.spyOn(weather.functions[0], "exec");
|
|
80
|
+
const response = await runResource({ paymentVoucher: voucherFor() });
|
|
81
|
+
expect(response.statusCode).toBe(status);
|
|
82
|
+
expect(response.json()).toEqual({ error });
|
|
83
|
+
expect(exec).not.toHaveBeenCalled();
|
|
84
|
+
await flushSettlement();
|
|
85
|
+
expect(capture).not.toHaveBeenCalled();
|
|
86
|
+
expect(release).not.toHaveBeenCalled();
|
|
66
87
|
});
|
|
67
88
|
test("refuses a voucher from a blocked caller, even for an unrestricted function", async () => {
|
|
68
89
|
await service.grantService.block(caller.publicKey);
|
|
@@ -70,7 +91,7 @@ describe("routes", () => {
|
|
|
70
91
|
expect(response.statusCode).toBe(403);
|
|
71
92
|
expect(response.json()).toEqual({ error: "Caller is blocked" });
|
|
72
93
|
await flushSettlement();
|
|
73
|
-
expect(
|
|
94
|
+
expect(capture).not.toHaveBeenCalled();
|
|
74
95
|
});
|
|
75
96
|
test("accepts a voucher that pays more than the cost", async () => {
|
|
76
97
|
const response = await runResource({ paymentVoucher: voucherFor("temperature", { amount: COST + 1 }) });
|
|
@@ -80,11 +101,11 @@ describe("routes", () => {
|
|
|
80
101
|
const response = await runResource({});
|
|
81
102
|
expect(response.statusCode).toBe(402);
|
|
82
103
|
await flushSettlement();
|
|
83
|
-
expect(
|
|
104
|
+
expect(capture).not.toHaveBeenCalled();
|
|
84
105
|
});
|
|
85
106
|
test.each([
|
|
86
107
|
["pays less than the cost", { amount: COST - 1 }],
|
|
87
|
-
["pays a different recipient", {
|
|
108
|
+
["pays a different recipient", { recipientOrganizationId: "org_other" }],
|
|
88
109
|
["has expired", { expiresAt: new Date(Date.now() - 1000).toISOString() }],
|
|
89
110
|
["has an unparseable expiry", { expiresAt: "not-a-date" }],
|
|
90
111
|
])("rejects a voucher that %s", async (_, overrides) => {
|
|
@@ -92,14 +113,14 @@ describe("routes", () => {
|
|
|
92
113
|
expect(response.statusCode).toBe(400);
|
|
93
114
|
expect(response.json()).toEqual({ error: "Invalid payment voucher" });
|
|
94
115
|
await flushSettlement();
|
|
95
|
-
expect(
|
|
116
|
+
expect(capture).not.toHaveBeenCalled();
|
|
96
117
|
});
|
|
97
118
|
test("rejects a voucher whose parameters were tampered with", async () => {
|
|
98
119
|
const response = await runResource({ paymentVoucher: voucherFor(), parameters: [{ name: "city", value: "Bergen" }] });
|
|
99
120
|
expect(response.statusCode).toBe(400);
|
|
100
121
|
});
|
|
101
122
|
test("rejects a voucher issued for a different function", async () => {
|
|
102
|
-
const paymentVoucher = callerInvoker.createPaymentVoucher(
|
|
123
|
+
const paymentVoucher = callerInvoker.createPaymentVoucher(ORGANIZATION, COST, weather.path, weather.name, "humidity", parameters);
|
|
103
124
|
const response = await runResource({ paymentVoucher });
|
|
104
125
|
expect(response.statusCode).toBe(400);
|
|
105
126
|
});
|
|
@@ -122,21 +143,23 @@ describe("routes", () => {
|
|
|
122
143
|
expect(response.statusCode).toBe(200);
|
|
123
144
|
expect(response.json()).toEqual({ value: 80, error: null });
|
|
124
145
|
await flushSettlement();
|
|
125
|
-
expect(
|
|
146
|
+
expect(capture).not.toHaveBeenCalled();
|
|
126
147
|
});
|
|
127
148
|
test("rejects an unknown function without settling the voucher", async () => {
|
|
128
149
|
const response = await runResource({ functionName: "missing", paymentVoucher: voucherFor("missing") });
|
|
129
150
|
expect(response.statusCode).toBe(400);
|
|
130
151
|
expect(response.json()).toEqual({ error: "Function not found" });
|
|
131
152
|
await flushSettlement();
|
|
132
|
-
expect(
|
|
153
|
+
expect(capture).not.toHaveBeenCalled();
|
|
133
154
|
});
|
|
134
|
-
test("
|
|
155
|
+
test("releases the hold when the invoke returns an error", async () => {
|
|
135
156
|
vi.spyOn(service.resourceInvoker, "invoke").mockResolvedValueOnce({ value: null, error: { message: "boom" } });
|
|
136
|
-
const
|
|
157
|
+
const paymentVoucher = voucherFor();
|
|
158
|
+
const response = await runResource({ paymentVoucher });
|
|
137
159
|
expect(response.statusCode).toBe(200);
|
|
138
160
|
await flushSettlement();
|
|
139
|
-
expect(
|
|
161
|
+
expect(capture).not.toHaveBeenCalled();
|
|
162
|
+
expect(release).toHaveBeenCalledExactlyOnceWith(paymentVoucher);
|
|
140
163
|
});
|
|
141
164
|
test("rejects an unknown resource", async () => {
|
|
142
165
|
const response = await runResource({ resourceName: "Missing" });
|
|
@@ -270,7 +293,7 @@ describe("routes", () => {
|
|
|
270
293
|
parameters: [{ name: "city", type: "string" }],
|
|
271
294
|
returnType: "string",
|
|
272
295
|
exec: async () => "sunny",
|
|
273
|
-
paymentManifest: {
|
|
296
|
+
paymentManifest: { costMicroUsd: 500, recipientOrganizationId: ORGANIZATION },
|
|
274
297
|
}],
|
|
275
298
|
});
|
|
276
299
|
function broadcast(resources, privateKey = keys.privateKey, publicKey = keys.publicKey) {
|
|
@@ -294,7 +317,7 @@ describe("routes", () => {
|
|
|
294
317
|
const resources = (await fastify.inject({ method: "GET", url: "/v1/resources" })).json().resources;
|
|
295
318
|
expect(resources.find((r) => r.name === "Forecast")).toMatchObject({
|
|
296
319
|
baseUrl: peer.baseUrl,
|
|
297
|
-
functions: [{ name: "tomorrow", paymentManifest: {
|
|
320
|
+
functions: [{ name: "tomorrow", paymentManifest: { costMicroUsd: 500, recipientOrganizationId: ORGANIZATION } }],
|
|
298
321
|
});
|
|
299
322
|
});
|
|
300
323
|
test("rejects a broadcast from an unknown peer", async () => {
|
|
@@ -366,7 +389,7 @@ describe("restricted functions", () => {
|
|
|
366
389
|
name: "Vault",
|
|
367
390
|
functions: [
|
|
368
391
|
{ name: "open", parameters: [{ name: "city", type: "string" }], returnType: "int32", exec: async () => 7, restricted: true },
|
|
369
|
-
{ name: "premium", parameters: [{ name: "city", type: "string" }], returnType: "int32", exec: async () => 9, restricted: true, paymentManifest: {
|
|
392
|
+
{ name: "premium", parameters: [{ name: "city", type: "string" }], returnType: "int32", exec: async () => 9, restricted: true, paymentManifest: { costMicroUsd: COST, recipientOrganizationId: ORGANIZATION } },
|
|
370
393
|
],
|
|
371
394
|
};
|
|
372
395
|
const call = (functionName) => ({ resourcePath: "com.example", resourceName: "Vault", functionName, parameters });
|
|
@@ -379,7 +402,9 @@ describe("restricted functions", () => {
|
|
|
379
402
|
beforeEach(async () => {
|
|
380
403
|
db = await openDatabase(":memory:");
|
|
381
404
|
service = createService(db, [restricted], testConfig());
|
|
382
|
-
vi.spyOn(service.operatorClient, "
|
|
405
|
+
vi.spyOn(service.operatorClient, "authorize").mockResolvedValue({ ok: true });
|
|
406
|
+
vi.spyOn(service.operatorClient, "capture").mockResolvedValue(undefined);
|
|
407
|
+
vi.spyOn(service.operatorClient, "release").mockResolvedValue(undefined);
|
|
383
408
|
callerDb = await openDatabase(":memory:");
|
|
384
409
|
callerGrants = new GrantService(new GrantRepository(callerDb), { baseUrl: "http://caller", ...caller });
|
|
385
410
|
grant = await service.grantService.issue(caller.publicKey, [{ resourcePath: "com.example", resourceName: "Vault" }], { lifetimeMs: 3_600_000 });
|
|
@@ -491,11 +516,11 @@ describe("restricted functions", () => {
|
|
|
491
516
|
expect(response.statusCode).toBe(403);
|
|
492
517
|
});
|
|
493
518
|
test("checks the authorization before the voucher, and needs both for a paid function", async () => {
|
|
494
|
-
const paymentVoucher = callerInvoker.createPaymentVoucher(
|
|
519
|
+
const paymentVoucher = callerInvoker.createPaymentVoucher(ORGANIZATION, COST, "com.example", "Vault", "premium", parameters);
|
|
495
520
|
const unauthorized = await runResource({ ...call("premium"), paymentVoucher });
|
|
496
521
|
expect(unauthorized.statusCode).toBe(403);
|
|
497
522
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
498
|
-
expect(service.operatorClient.
|
|
523
|
+
expect(service.operatorClient.authorize).not.toHaveBeenCalled();
|
|
499
524
|
const unpaid = await runResource({ ...call("premium"), authorization: callerGrants.authorize(grant, call("premium")) });
|
|
500
525
|
expect(unpaid.statusCode).toBe(402);
|
|
501
526
|
const paid = await runResource({ ...call("premium"), paymentVoucher, authorization: callerGrants.authorize(grant, call("premium")) });
|
|
@@ -571,15 +596,19 @@ describe("inference functions", () => {
|
|
|
571
596
|
// "Hello world!" is 3 input tokens by the fixture's estimator. 3 at $3/M plus 1,000 at $15/M, rounded up.
|
|
572
597
|
const parameters = [{ name: "prompt", value: "Hello world!" }, { name: "maxTokens", value: 1000 }];
|
|
573
598
|
const MAX_AMOUNT = 15_009;
|
|
574
|
-
const manifest = { ...INFERENCE_RATES,
|
|
599
|
+
const manifest = { ...INFERENCE_RATES, recipientOrganizationId: ORGANIZATION };
|
|
575
600
|
let db;
|
|
576
601
|
let service;
|
|
577
602
|
let fastify;
|
|
578
|
-
let
|
|
603
|
+
let authorize;
|
|
604
|
+
let capture;
|
|
605
|
+
let release;
|
|
579
606
|
beforeEach(async () => {
|
|
580
607
|
db = await openDatabase(":memory:");
|
|
581
608
|
service = createService(db, [weather, model], testConfig());
|
|
582
|
-
|
|
609
|
+
authorize = vi.spyOn(service.operatorClient, "authorize").mockResolvedValue({ ok: true });
|
|
610
|
+
capture = vi.spyOn(service.operatorClient, "capture").mockResolvedValue(undefined);
|
|
611
|
+
release = vi.spyOn(service.operatorClient, "release").mockResolvedValue(undefined);
|
|
583
612
|
fastify = Fastify();
|
|
584
613
|
await fastify.register(routes, { prefix: "/v1", service });
|
|
585
614
|
await fastify.ready();
|
|
@@ -639,7 +668,7 @@ describe("inference functions", () => {
|
|
|
639
668
|
expect(response.statusCode).toBe(200);
|
|
640
669
|
expect(response.json()).toEqual({ value: "echo: Hello world!", error: null, usage: { inputTokens: 3, outputTokens: 1000 } });
|
|
641
670
|
await flushSettlement();
|
|
642
|
-
expect(
|
|
671
|
+
expect(capture).toHaveBeenCalledExactlyOnceWith({ voucher: paymentVoucher, usage: { inputTokens: 3, outputTokens: 1000 } });
|
|
643
672
|
});
|
|
644
673
|
test("refuses a voucher below the estimator's requirement without running inference", async () => {
|
|
645
674
|
const exec = vi.spyOn(model.functions[0], "exec");
|
|
@@ -648,20 +677,20 @@ describe("inference functions", () => {
|
|
|
648
677
|
expect(response.json()).toMatchObject({ requiredMaxAmount: MAX_AMOUNT });
|
|
649
678
|
expect(exec).not.toHaveBeenCalled();
|
|
650
679
|
await flushSettlement();
|
|
651
|
-
expect(
|
|
680
|
+
expect(capture).not.toHaveBeenCalled();
|
|
652
681
|
});
|
|
653
682
|
test.each([
|
|
654
|
-
["a different input rate", {
|
|
655
|
-
["a different output rate", {
|
|
683
|
+
["a different input rate", { inputCostMicroUsdPerMillionTokens: 1 }],
|
|
684
|
+
["a different output rate", { outputCostMicroUsdPerMillionTokens: 1 }],
|
|
656
685
|
["a maxTokens other than the call's", { maxTokens: 999 }],
|
|
657
|
-
["a different recipient", {
|
|
686
|
+
["a different recipient", { recipientOrganizationId: "org_other" }],
|
|
658
687
|
])("rejects a voucher with %s", async (_, overrides) => {
|
|
659
688
|
const response = await call("/v1/run/resource", { paymentVoucher: voucherFor("complete", overrides) });
|
|
660
689
|
expect(response.statusCode).toBe(400);
|
|
661
690
|
expect(response.json()).toEqual({ error: "Invalid payment voucher" });
|
|
662
691
|
});
|
|
663
692
|
test("rejects a fixed voucher for an inference function, and an inference voucher for a fixed function", async () => {
|
|
664
|
-
const fixed = callerInvoker.createPaymentVoucher(
|
|
693
|
+
const fixed = callerInvoker.createPaymentVoucher(ORGANIZATION, MAX_AMOUNT, model.path, model.name, "complete", parameters);
|
|
665
694
|
expect((await call("/v1/run/resource", { paymentVoucher: fixed })).statusCode).toBe(400);
|
|
666
695
|
const weatherParameters = [{ name: "city", value: "Oslo" }];
|
|
667
696
|
const inference = callerInvoker.createInferenceVoucher(manifest, MAX_AMOUNT, 1000, weather.path, weather.name, "temperature", weatherParameters);
|
|
@@ -674,21 +703,25 @@ describe("inference functions", () => {
|
|
|
674
703
|
const response = await call("/v1/run/resource", { paymentVoucher });
|
|
675
704
|
expect(response.json().usage).toEqual({ inputTokens: 3, outputTokens: 1000 });
|
|
676
705
|
await flushSettlement();
|
|
677
|
-
expect(
|
|
706
|
+
expect(capture).toHaveBeenCalledExactlyOnceWith({ voucher: paymentVoucher, usage: { inputTokens: 3, outputTokens: 1000 } });
|
|
678
707
|
});
|
|
679
|
-
test("
|
|
708
|
+
test("releases the hold of a call that charges nothing", async () => {
|
|
680
709
|
vi.spyOn(model.functions[0], "exec").mockResolvedValue({ value: "", usage: { inputTokens: 0, outputTokens: 0 } });
|
|
681
|
-
const
|
|
710
|
+
const paymentVoucher = voucherFor();
|
|
711
|
+
const response = await call("/v1/run/resource", { paymentVoucher });
|
|
682
712
|
expect(response.statusCode).toBe(200);
|
|
683
713
|
await flushSettlement();
|
|
684
|
-
expect(
|
|
714
|
+
expect(capture).not.toHaveBeenCalled();
|
|
715
|
+
expect(release).toHaveBeenCalledExactlyOnceWith(paymentVoucher);
|
|
685
716
|
});
|
|
686
|
-
test("
|
|
717
|
+
test("releases the hold when the function doesn't report usage", async () => {
|
|
687
718
|
vi.spyOn(model.functions[0], "exec").mockResolvedValue("no usage");
|
|
688
|
-
const
|
|
719
|
+
const paymentVoucher = voucherFor();
|
|
720
|
+
const response = await call("/v1/run/resource", { paymentVoucher });
|
|
689
721
|
expect(response.json().error.message).toContain("must return { value, usage");
|
|
690
722
|
await flushSettlement();
|
|
691
|
-
expect(
|
|
723
|
+
expect(capture).not.toHaveBeenCalled();
|
|
724
|
+
expect(release).toHaveBeenCalledExactlyOnceWith(paymentVoucher);
|
|
692
725
|
});
|
|
693
726
|
});
|
|
694
727
|
test("GET /v1/resources announces the kind and per-token pricing", async () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import { DEFAULT_COST_LIMIT_MICRO_USD, resolveConfig } from "../server.js";
|
|
3
3
|
import { modelResource, weatherResource } from "./helpers.js";
|
|
4
4
|
describe("resolveConfig", () => {
|
|
5
5
|
const keys = { baseUrl: "http://node.example", publicKey: "public", privateKey: "private" };
|
|
@@ -15,15 +15,15 @@ describe("resolveConfig", () => {
|
|
|
15
15
|
vi.stubEnv("BASE_URL", "http://env.example");
|
|
16
16
|
vi.stubEnv("SIGNING_PUBLIC_KEY", "env-public");
|
|
17
17
|
vi.stubEnv("SIGNING_PRIVATE_KEY", "env-private");
|
|
18
|
-
vi.stubEnv("
|
|
19
|
-
expect(resolveConfig({})).toMatchObject({ baseUrl: "http://env.example", publicKey: "env-public", privateKey: "env-private",
|
|
18
|
+
vi.stubEnv("COST_LIMIT_MICRO_USD", "500");
|
|
19
|
+
expect(resolveConfig({})).toMatchObject({ baseUrl: "http://env.example", publicKey: "env-public", privateKey: "env-private", costLimitMicroUsd: 500 });
|
|
20
20
|
});
|
|
21
21
|
test("defaults the cost limit", () => {
|
|
22
|
-
vi.stubEnv("
|
|
23
|
-
expect(resolveConfig(keys).
|
|
22
|
+
vi.stubEnv("COST_LIMIT_MICRO_USD", undefined);
|
|
23
|
+
expect(resolveConfig(keys).costLimitMicroUsd).toBe(DEFAULT_COST_LIMIT_MICRO_USD);
|
|
24
24
|
});
|
|
25
|
-
test.each([-1, 1.5, Number.NaN])("rejects a cost limit of %s", (
|
|
26
|
-
expect(() => resolveConfig({ ...keys,
|
|
25
|
+
test.each([-1, 1.5, Number.NaN])("rejects a cost limit of %s", (costLimitMicroUsd) => {
|
|
26
|
+
expect(() => resolveConfig({ ...keys, costLimitMicroUsd })).toThrow("costLimitMicroUsd");
|
|
27
27
|
});
|
|
28
28
|
test("requires the signing keys and base URL", () => {
|
|
29
29
|
vi.stubEnv("SIGNING_PRIVATE_KEY", undefined);
|
|
@@ -35,12 +35,12 @@ describe("resolveConfig", () => {
|
|
|
35
35
|
expect(() => resolveConfig(keys, [weatherResource()])).toThrow("OPERATOR_URL is required");
|
|
36
36
|
});
|
|
37
37
|
test("defaults the inference cost limit to the cost limit, and reads its own option or environment variable", () => {
|
|
38
|
-
vi.stubEnv("
|
|
39
|
-
expect(resolveConfig({ ...keys,
|
|
40
|
-
vi.stubEnv("
|
|
41
|
-
expect(resolveConfig({ ...keys,
|
|
42
|
-
expect(resolveConfig({ ...keys,
|
|
43
|
-
expect(() => resolveConfig({ ...keys,
|
|
38
|
+
vi.stubEnv("INFERENCE_COST_LIMIT_MICRO_USD", undefined);
|
|
39
|
+
expect(resolveConfig({ ...keys, costLimitMicroUsd: 700 }).inferenceCostLimitMicroUsd).toBe(700);
|
|
40
|
+
vi.stubEnv("INFERENCE_COST_LIMIT_MICRO_USD", "900");
|
|
41
|
+
expect(resolveConfig({ ...keys, costLimitMicroUsd: 700 }).inferenceCostLimitMicroUsd).toBe(900);
|
|
42
|
+
expect(resolveConfig({ ...keys, inferenceCostLimitMicroUsd: 1_000 }).inferenceCostLimitMicroUsd).toBe(1_000);
|
|
43
|
+
expect(() => resolveConfig({ ...keys, inferenceCostLimitMicroUsd: -1 })).toThrow("inferenceCostLimitMicroUsd");
|
|
44
44
|
});
|
|
45
45
|
test("accepts well-declared inference functions", () => {
|
|
46
46
|
expect(() => resolveConfig({ ...keys, operatorUrl: "http://operator.example" }, [modelResource()])).not.toThrow();
|
|
@@ -49,8 +49,8 @@ describe("resolveConfig", () => {
|
|
|
49
49
|
["without a maxTokens parameter", { parameters: [{ name: "prompt", type: "string" }] }, "maxTokens: int32"],
|
|
50
50
|
["whose maxTokens isn't an int32", { parameters: [{ name: "maxTokens", type: "int64" }] }, "maxTokens: int32"],
|
|
51
51
|
["that is paid but has no estimator", { estimateInputTokens: undefined }, "estimateInputTokens"],
|
|
52
|
-
["priced with a flat cost", { paymentManifest: {
|
|
53
|
-
["with a negative rate", { paymentManifest: {
|
|
52
|
+
["priced with a flat cost", { paymentManifest: { costMicroUsd: 5, recipientOrganizationId: "r" } }, "per million tokens"],
|
|
53
|
+
["with a negative rate", { paymentManifest: { inputCostMicroUsdPerMillionTokens: -1, outputCostMicroUsdPerMillionTokens: 0, recipientOrganizationId: "r" } }, "per million tokens"],
|
|
54
54
|
])("rejects an inference function %s", (_, overrides, message) => {
|
|
55
55
|
const model = modelResource();
|
|
56
56
|
model.functions = [{ ...model.functions[0], ...overrides }];
|