@jeffusion/bungee-core 5.10.0 → 5.11.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/main.js +31344 -17334
- package/dist/plugins/ai-transformer/index.js.map +2 -2
- package/dist/plugins/anthropic-request-sanitizer/index.js.map +2 -2
- package/dist/plugins/anthropic-tool-name-transformer/index.js.map +2 -2
- package/dist/plugins/chatgpt-oauth/control.js +513 -1
- package/dist/plugins/chatgpt-oauth/control.js.map +12 -4
- package/dist/plugins/chatgpt-oauth/index.js +511 -4
- package/dist/plugins/chatgpt-oauth/index.js.map +13 -5
- package/dist/plugins/chatgpt-oauth/manifest.json +19 -0
- package/dist/plugins/deepseek-reasoning-fix/index.js.map +2 -2
- package/dist/plugins/key-access/index.js.map +2 -2
- package/dist/plugins/key-rate-limit/index.js.map +2 -2
- package/dist/plugins/local-accounts/control.js.map +2 -2
- package/dist/plugins/local-accounts/index.js.map +2 -2
- package/dist/plugins/model-mapping/control.js +72 -17741
- package/dist/plugins/model-mapping/control.js.map +6 -58
- package/dist/plugins/model-mapping/index.js +32 -17433
- package/dist/plugins/model-mapping/index.js.map +6 -58
- package/dist/plugins/model-mapping/manifest.json +30 -14
- package/dist/plugins/models-dev/control.js +1042 -0
- package/dist/plugins/models-dev/control.js.map +16 -0
- package/dist/plugins/models-dev/index.js +15418 -0
- package/dist/plugins/models-dev/index.js.map +134 -0
- package/dist/plugins/models-dev/manifest.json +199 -0
- package/dist/plugins/models-dev/ui/ModelsDevSettings.svelte +120 -0
- package/dist/plugins/openai-messages-to-chat/index.js.map +2 -2
- package/dist/plugins/signature-repair/index.js.map +2 -2
- package/dist/plugins/token-budget/control.js +524 -9
- package/dist/plugins/token-budget/control.js.map +12 -4
- package/dist/plugins/token-budget/index.js +519 -12
- package/dist/plugins/token-budget/index.js.map +13 -6
- package/dist/plugins/token-budget/manifest.json +16 -2
- package/dist/plugins/token-metering/index.js +93 -2
- package/dist/plugins/token-metering/index.js.map +12 -6
- package/dist/plugins/token-stats/control.js +174 -312
- package/dist/plugins/token-stats/control.js.map +7 -7
- package/dist/plugins/token-stats/index.js +283 -486
- package/dist/plugins/token-stats/index.js.map +16 -12
- package/dist/plugins/token-stats/manifest.json +99 -12
- package/dist/plugins/token-stats/ui/ClientModelPicker.svelte +76 -0
- package/dist/plugins/token-stats/ui/PriceModelPicker.svelte +87 -0
- package/dist/plugins/token-stats/ui/TokenStatsSettings.svelte +136 -80
- package/dist/plugins/token-stats/ui/client-model-search.test.ts +58 -0
- package/dist/plugins/token-stats/ui/client-model-search.ts +16 -0
- package/dist/plugins/token-stats/ui/model-search.ts +45 -0
- package/dist/plugins/token-stats/ui/pricing-catalog.test.ts +114 -0
- package/dist/plugins/token-stats/ui/pricing-catalog.ts +17 -0
- package/package.json +2 -2
|
@@ -1,4 +1,512 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// packages/core/src/plugin-services/host-rpc.ts
|
|
3
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
4
|
+
|
|
5
|
+
// packages/core/src/plugin-services/wire-contract.ts
|
|
6
|
+
import { isProxy } from "util/types";
|
|
7
|
+
var RPC_JSON_MAX_BYTES = 64 * 1024;
|
|
8
|
+
var RPC_CONTRACT_MAX_BYTES = 256 * 1024;
|
|
9
|
+
var JSON_MAX_DEPTH = 64;
|
|
10
|
+
var SCHEMA_MAX_DEPTH = 64;
|
|
11
|
+
var RPC_MAX_TIMEOUT_MS = 300000;
|
|
12
|
+
var RPC_MAX_METHODS = 128;
|
|
13
|
+
var RPC_SERVICE_ID = /^[a-z][a-z0-9.-]{0,127}$/;
|
|
14
|
+
var RPC_METHOD_NAME = /^[A-Za-z_][A-Za-z0-9_.-]{0,127}$/;
|
|
15
|
+
var RPC_CALL_PURPOSES = new Set(["bootstrap", "background", "management", "request", "attempt"]);
|
|
16
|
+
var RPC_COMMAND_DEDUPLICATION = new Set(["local-transaction", "external-contract", "none"]);
|
|
17
|
+
var RPC_COMMAND_POLICY_KEYS = new Set(["deduplication", "resultRetentionMs", "quotaBytes", "maxResultBytes"]);
|
|
18
|
+
var CONTRACT_KEYS = ["id", "version", "methods"];
|
|
19
|
+
var METHOD_KEYS = [
|
|
20
|
+
"kind",
|
|
21
|
+
"input",
|
|
22
|
+
"output",
|
|
23
|
+
"purposes",
|
|
24
|
+
"timeoutMs",
|
|
25
|
+
"maxInputBytes",
|
|
26
|
+
"maxOutputBytes",
|
|
27
|
+
"command"
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
class RpcProtocolError extends Error {
|
|
31
|
+
code;
|
|
32
|
+
name = "RpcProtocolError";
|
|
33
|
+
constructor(code, message) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.code = code;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function failData(category) {
|
|
39
|
+
throw new RpcProtocolError("invalid_data", `invalid_data in ${category}`);
|
|
40
|
+
}
|
|
41
|
+
function failSize() {
|
|
42
|
+
throw new RpcProtocolError("size_limit", "size_limit: JSON value exceeds the byte limit");
|
|
43
|
+
}
|
|
44
|
+
function failContract(reason) {
|
|
45
|
+
throw new RpcProtocolError("invalid_contract", `invalid_contract: ${reason}`);
|
|
46
|
+
}
|
|
47
|
+
function isPlainObject(value) {
|
|
48
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
49
|
+
return false;
|
|
50
|
+
if (isProxy(value))
|
|
51
|
+
return false;
|
|
52
|
+
const prototype = Object.getPrototypeOf(value);
|
|
53
|
+
return prototype === Object.prototype || prototype === null;
|
|
54
|
+
}
|
|
55
|
+
function isPositiveSafeInteger(value) {
|
|
56
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
57
|
+
}
|
|
58
|
+
function isNonNegativeSafeInteger(value) {
|
|
59
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
60
|
+
}
|
|
61
|
+
function hasLoneSurrogate(value) {
|
|
62
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
63
|
+
const code = value.charCodeAt(index);
|
|
64
|
+
if (code >= 55296 && code <= 56319) {
|
|
65
|
+
if (index + 1 >= value.length)
|
|
66
|
+
return true;
|
|
67
|
+
const next = value.charCodeAt(index + 1);
|
|
68
|
+
if (next < 56320 || next > 57343)
|
|
69
|
+
return true;
|
|
70
|
+
index += 1;
|
|
71
|
+
} else if (code >= 56320 && code <= 57343) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
function isArrayIndex(key, length) {
|
|
78
|
+
if (!/^(?:0|[1-9]\d*)$/.test(key))
|
|
79
|
+
return false;
|
|
80
|
+
const index = Number(key);
|
|
81
|
+
return Number.isSafeInteger(index) && index >= 0 && index < length;
|
|
82
|
+
}
|
|
83
|
+
function assertKnownKeys(input, allowed, category) {
|
|
84
|
+
for (const key of Object.keys(input)) {
|
|
85
|
+
if (!allowed.includes(key))
|
|
86
|
+
failContract(`unknown ${category} field`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function spend(budget, bytes) {
|
|
90
|
+
budget.left -= bytes;
|
|
91
|
+
if (budget.left < 0)
|
|
92
|
+
failSize();
|
|
93
|
+
}
|
|
94
|
+
function snapshot(value, depth, ancestors, budget) {
|
|
95
|
+
if (value === null) {
|
|
96
|
+
spend(budget, 4);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
if (typeof value === "boolean") {
|
|
100
|
+
spend(budget, value ? 4 : 5);
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
if (typeof value === "string") {
|
|
104
|
+
if (hasLoneSurrogate(value))
|
|
105
|
+
failData("string");
|
|
106
|
+
spend(budget, Buffer.byteLength(value, "utf8") + 2);
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
if (typeof value === "number") {
|
|
110
|
+
if (!Number.isFinite(value))
|
|
111
|
+
failData("number");
|
|
112
|
+
spend(budget, 1);
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
if (typeof value !== "object")
|
|
116
|
+
failData("json");
|
|
117
|
+
const object = value;
|
|
118
|
+
if (isProxy(object))
|
|
119
|
+
failData("json");
|
|
120
|
+
if (ancestors.has(object))
|
|
121
|
+
failData("json");
|
|
122
|
+
const nextDepth = depth + 1;
|
|
123
|
+
if (nextDepth > JSON_MAX_DEPTH)
|
|
124
|
+
failData("json");
|
|
125
|
+
if (Array.isArray(object)) {
|
|
126
|
+
if (Object.getPrototypeOf(object) !== Array.prototype)
|
|
127
|
+
failData("array");
|
|
128
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(object, "length");
|
|
129
|
+
if (!lengthDescriptor || !("value" in lengthDescriptor) || typeof lengthDescriptor.value !== "number") {
|
|
130
|
+
failData("array");
|
|
131
|
+
}
|
|
132
|
+
const length = lengthDescriptor.value;
|
|
133
|
+
if (!Number.isSafeInteger(length) || length < 0)
|
|
134
|
+
failData("array");
|
|
135
|
+
if (length > budget.left)
|
|
136
|
+
failSize();
|
|
137
|
+
spend(budget, 2);
|
|
138
|
+
if (length > 1)
|
|
139
|
+
spend(budget, length - 1);
|
|
140
|
+
ancestors.add(object);
|
|
141
|
+
const items = new Array(length);
|
|
142
|
+
let count = 0;
|
|
143
|
+
for (const key of Reflect.ownKeys(object)) {
|
|
144
|
+
if (key === "length")
|
|
145
|
+
continue;
|
|
146
|
+
if (typeof key !== "string" || !isArrayIndex(key, length))
|
|
147
|
+
failData("array");
|
|
148
|
+
const descriptor = Object.getOwnPropertyDescriptor(object, key);
|
|
149
|
+
if (!descriptor || !descriptor.enumerable || !("value" in descriptor))
|
|
150
|
+
failData("array");
|
|
151
|
+
items[Number(key)] = snapshot(descriptor.value, nextDepth, ancestors, budget);
|
|
152
|
+
count += 1;
|
|
153
|
+
}
|
|
154
|
+
if (count !== length)
|
|
155
|
+
failData("array");
|
|
156
|
+
ancestors.delete(object);
|
|
157
|
+
return items;
|
|
158
|
+
}
|
|
159
|
+
const prototype = Object.getPrototypeOf(object);
|
|
160
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
161
|
+
failData("object");
|
|
162
|
+
const keys = Reflect.ownKeys(object);
|
|
163
|
+
if (keys.length > budget.left)
|
|
164
|
+
failSize();
|
|
165
|
+
spend(budget, 2);
|
|
166
|
+
if (keys.length > 1)
|
|
167
|
+
spend(budget, keys.length - 1);
|
|
168
|
+
ancestors.add(object);
|
|
169
|
+
const result = {};
|
|
170
|
+
for (const key of keys) {
|
|
171
|
+
if (typeof key !== "string")
|
|
172
|
+
failData("object");
|
|
173
|
+
if (hasLoneSurrogate(key))
|
|
174
|
+
failData("object");
|
|
175
|
+
const descriptor = Object.getOwnPropertyDescriptor(object, key);
|
|
176
|
+
if (!descriptor || !descriptor.enumerable || !("value" in descriptor))
|
|
177
|
+
failData("object");
|
|
178
|
+
spend(budget, Buffer.byteLength(key, "utf8") + 3);
|
|
179
|
+
Object.defineProperty(result, key, {
|
|
180
|
+
value: snapshot(descriptor.value, nextDepth, ancestors, budget),
|
|
181
|
+
enumerable: true,
|
|
182
|
+
writable: true,
|
|
183
|
+
configurable: true
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
ancestors.delete(object);
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
function snapshotPureInput(input) {
|
|
190
|
+
try {
|
|
191
|
+
const pure = snapshot(input, 0, new Set, { left: RPC_CONTRACT_MAX_BYTES });
|
|
192
|
+
if (Buffer.byteLength(JSON.stringify(pure), "utf8") > RPC_CONTRACT_MAX_BYTES)
|
|
193
|
+
failContract("input must be bounded pure JSON data");
|
|
194
|
+
return pure;
|
|
195
|
+
} catch (error) {
|
|
196
|
+
if (error instanceof RpcProtocolError && (error.code === "invalid_data" || error.code === "size_limit")) {
|
|
197
|
+
failContract("input must be bounded pure JSON data");
|
|
198
|
+
}
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function checkOptionalNonNegative(value, field) {
|
|
203
|
+
if (value === undefined)
|
|
204
|
+
return;
|
|
205
|
+
if (!isNonNegativeSafeInteger(value))
|
|
206
|
+
failContract(`${field} must be a non-negative safe integer`);
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
function checkOptionalFinite(value, field) {
|
|
210
|
+
if (value === undefined)
|
|
211
|
+
return;
|
|
212
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
213
|
+
failContract(`${field} must be a finite number`);
|
|
214
|
+
return value;
|
|
215
|
+
}
|
|
216
|
+
function checkSchema(input, depth, seen) {
|
|
217
|
+
if (depth > SCHEMA_MAX_DEPTH)
|
|
218
|
+
failContract("schema nesting exceeds the limit");
|
|
219
|
+
if (!isPlainObject(input))
|
|
220
|
+
failContract("schema must be a plain object");
|
|
221
|
+
if (seen.has(input))
|
|
222
|
+
failContract("schema must not contain cycles");
|
|
223
|
+
seen.add(input);
|
|
224
|
+
const type = input.type;
|
|
225
|
+
if (type === "string") {
|
|
226
|
+
assertKnownKeys(input, ["type", "minLength", "maxLength"], "schema");
|
|
227
|
+
const min = checkOptionalNonNegative(input.minLength, "minLength");
|
|
228
|
+
const max = checkOptionalNonNegative(input.maxLength, "maxLength");
|
|
229
|
+
if (min !== undefined && max !== undefined && min > max)
|
|
230
|
+
failContract("string minLength must not exceed maxLength");
|
|
231
|
+
} else if (type === "number") {
|
|
232
|
+
assertKnownKeys(input, ["type", "integer", "minimum", "maximum"], "schema");
|
|
233
|
+
if (input.integer !== undefined && typeof input.integer !== "boolean")
|
|
234
|
+
failContract("number integer flag must be boolean");
|
|
235
|
+
const min = checkOptionalFinite(input.minimum, "minimum");
|
|
236
|
+
const max = checkOptionalFinite(input.maximum, "maximum");
|
|
237
|
+
if (min !== undefined && max !== undefined && min > max)
|
|
238
|
+
failContract("number minimum must not exceed maximum");
|
|
239
|
+
} else if (type === "boolean") {
|
|
240
|
+
assertKnownKeys(input, ["type"], "schema");
|
|
241
|
+
} else if (type === "null") {
|
|
242
|
+
assertKnownKeys(input, ["type"], "schema");
|
|
243
|
+
} else if (type === "literal") {
|
|
244
|
+
assertKnownKeys(input, ["type", "value"], "schema");
|
|
245
|
+
if (!Object.prototype.hasOwnProperty.call(input, "value"))
|
|
246
|
+
failContract("literal schema requires a value");
|
|
247
|
+
try {
|
|
248
|
+
snapshot(input.value, 0, new Set, { left: Infinity });
|
|
249
|
+
} catch (error) {
|
|
250
|
+
if (error instanceof RpcProtocolError && error.code === "invalid_data") {
|
|
251
|
+
failContract("literal value must be pure JSON");
|
|
252
|
+
}
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
} else if (type === "array") {
|
|
256
|
+
assertKnownKeys(input, ["type", "items", "maxItems"], "schema");
|
|
257
|
+
if (!Object.prototype.hasOwnProperty.call(input, "items"))
|
|
258
|
+
failContract("array schema requires items");
|
|
259
|
+
checkSchema(input.items, depth + 1, seen);
|
|
260
|
+
checkOptionalNonNegative(input.maxItems, "maxItems");
|
|
261
|
+
} else if (type === "object") {
|
|
262
|
+
assertKnownKeys(input, ["type", "properties", "optional"], "schema");
|
|
263
|
+
const properties = input.properties;
|
|
264
|
+
if (!isPlainObject(properties))
|
|
265
|
+
failContract("object schema requires a properties record");
|
|
266
|
+
for (const key of Object.keys(properties))
|
|
267
|
+
checkSchema(properties[key], depth + 1, seen);
|
|
268
|
+
const optional = input.optional;
|
|
269
|
+
if (optional !== undefined) {
|
|
270
|
+
if (!Array.isArray(optional))
|
|
271
|
+
failContract("object optional must be an array of field names");
|
|
272
|
+
const names = new Set;
|
|
273
|
+
for (const name of optional) {
|
|
274
|
+
if (typeof name !== "string")
|
|
275
|
+
failContract("object optional entries must be strings");
|
|
276
|
+
if (names.has(name))
|
|
277
|
+
failContract("object optional entries must be unique");
|
|
278
|
+
names.add(name);
|
|
279
|
+
if (!Object.prototype.hasOwnProperty.call(properties, name)) {
|
|
280
|
+
failContract("object optional names must exist in properties");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
} else if (type === "record") {
|
|
285
|
+
assertKnownKeys(input, ["type", "values", "maxEntries"], "schema");
|
|
286
|
+
if (!Object.prototype.hasOwnProperty.call(input, "values"))
|
|
287
|
+
failContract("record schema requires values");
|
|
288
|
+
checkSchema(input.values, depth + 1, seen);
|
|
289
|
+
checkOptionalNonNegative(input.maxEntries, "maxEntries");
|
|
290
|
+
} else if (type === "union") {
|
|
291
|
+
assertKnownKeys(input, ["type", "variants"], "schema");
|
|
292
|
+
const variants = input.variants;
|
|
293
|
+
if (!Array.isArray(variants) || variants.length === 0) {
|
|
294
|
+
failContract("union schema requires a non-empty variants array");
|
|
295
|
+
}
|
|
296
|
+
for (const variant of variants)
|
|
297
|
+
checkSchema(variant, depth + 1, seen);
|
|
298
|
+
} else if (type === "json") {
|
|
299
|
+
assertKnownKeys(input, ["type", "maxBytes"], "schema");
|
|
300
|
+
if (input.maxBytes !== undefined && !isPositiveSafeInteger(input.maxBytes)) {
|
|
301
|
+
failContract("json schema maxBytes must be a positive safe integer");
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
failContract("unknown schema type");
|
|
305
|
+
}
|
|
306
|
+
seen.delete(input);
|
|
307
|
+
}
|
|
308
|
+
function checkByteLimit(value, field) {
|
|
309
|
+
if (value === undefined)
|
|
310
|
+
return;
|
|
311
|
+
if (!isPositiveSafeInteger(value) || value > RPC_JSON_MAX_BYTES) {
|
|
312
|
+
failContract(`${field} must be a positive integer no larger than the RPC envelope`);
|
|
313
|
+
}
|
|
314
|
+
return value;
|
|
315
|
+
}
|
|
316
|
+
function checkCommandPolicy(policy, maxOutputBytes) {
|
|
317
|
+
const keys = Object.keys(policy);
|
|
318
|
+
if (keys.length !== RPC_COMMAND_POLICY_KEYS.size || keys.some((key) => !RPC_COMMAND_POLICY_KEYS.has(key))) {
|
|
319
|
+
failContract("command policy fields are fixed");
|
|
320
|
+
}
|
|
321
|
+
const deduplication = policy.deduplication;
|
|
322
|
+
if (typeof deduplication !== "string" || !RPC_COMMAND_DEDUPLICATION.has(deduplication)) {
|
|
323
|
+
failContract("invalid command deduplication");
|
|
324
|
+
}
|
|
325
|
+
const retention = policy.resultRetentionMs;
|
|
326
|
+
if (retention !== null && !isPositiveSafeInteger(retention)) {
|
|
327
|
+
failContract("resultRetentionMs must be a positive integer or null");
|
|
328
|
+
}
|
|
329
|
+
const maxResultBytes = policy.maxResultBytes;
|
|
330
|
+
if (!isPositiveSafeInteger(maxResultBytes))
|
|
331
|
+
failContract("maxResultBytes must be a positive safe integer");
|
|
332
|
+
if (maxResultBytes > maxOutputBytes)
|
|
333
|
+
failContract("maxResultBytes must not exceed maxOutputBytes");
|
|
334
|
+
const quotaBytes = policy.quotaBytes;
|
|
335
|
+
if (!isPositiveSafeInteger(quotaBytes))
|
|
336
|
+
failContract("quotaBytes must be a positive safe integer");
|
|
337
|
+
if (quotaBytes < maxResultBytes)
|
|
338
|
+
failContract("quotaBytes must be able to hold maxResultBytes");
|
|
339
|
+
}
|
|
340
|
+
function checkMethod(definition) {
|
|
341
|
+
assertKnownKeys(definition, METHOD_KEYS, "method");
|
|
342
|
+
const kind = definition.kind;
|
|
343
|
+
if (kind !== "query" && kind !== "command")
|
|
344
|
+
failContract("method kind must be query or command");
|
|
345
|
+
checkSchema(definition.input, 0, new Set);
|
|
346
|
+
checkSchema(definition.output, 0, new Set);
|
|
347
|
+
const purposes = definition.purposes;
|
|
348
|
+
if (!Array.isArray(purposes) || purposes.length === 0)
|
|
349
|
+
failContract("purposes must be a non-empty array");
|
|
350
|
+
const seenPurposes = new Set;
|
|
351
|
+
for (const purpose of purposes) {
|
|
352
|
+
if (typeof purpose !== "string" || !RPC_CALL_PURPOSES.has(purpose))
|
|
353
|
+
failContract("invalid purpose");
|
|
354
|
+
if (seenPurposes.has(purpose))
|
|
355
|
+
failContract("purposes must be unique");
|
|
356
|
+
seenPurposes.add(purpose);
|
|
357
|
+
}
|
|
358
|
+
if (definition.timeoutMs !== undefined) {
|
|
359
|
+
if (!isPositiveSafeInteger(definition.timeoutMs) || definition.timeoutMs > RPC_MAX_TIMEOUT_MS) {
|
|
360
|
+
failContract("timeoutMs must be a positive integer no larger than the timeout bound");
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
checkByteLimit(definition.maxInputBytes, "maxInputBytes");
|
|
364
|
+
const maxOutputBytes = checkByteLimit(definition.maxOutputBytes, "maxOutputBytes");
|
|
365
|
+
const command = definition.command;
|
|
366
|
+
if (kind === "command") {
|
|
367
|
+
if (!isPlainObject(command))
|
|
368
|
+
failContract("command methods require an explicit command policy");
|
|
369
|
+
checkCommandPolicy(command, maxOutputBytes ?? RPC_JSON_MAX_BYTES);
|
|
370
|
+
} else if (command !== undefined) {
|
|
371
|
+
failContract("query methods must not declare a command policy");
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function checkContract(contract) {
|
|
375
|
+
if (!isPlainObject(contract))
|
|
376
|
+
failContract("contract must be a plain object");
|
|
377
|
+
assertKnownKeys(contract, CONTRACT_KEYS, "contract");
|
|
378
|
+
if (typeof contract.id !== "string" || !RPC_SERVICE_ID.test(contract.id))
|
|
379
|
+
failContract("invalid service id");
|
|
380
|
+
if (!isPositiveSafeInteger(contract.version))
|
|
381
|
+
failContract("service version must be a positive safe integer");
|
|
382
|
+
const methods = contract.methods;
|
|
383
|
+
if (!isPlainObject(methods))
|
|
384
|
+
failContract("methods must be a plain object");
|
|
385
|
+
const names = Object.keys(methods);
|
|
386
|
+
if (names.length === 0)
|
|
387
|
+
failContract("service must declare at least one method");
|
|
388
|
+
if (names.length > RPC_MAX_METHODS)
|
|
389
|
+
failContract("service declares too many methods");
|
|
390
|
+
for (const name of names) {
|
|
391
|
+
if (!RPC_METHOD_NAME.test(name))
|
|
392
|
+
failContract("invalid method name");
|
|
393
|
+
const definition = methods[name];
|
|
394
|
+
if (!isPlainObject(definition))
|
|
395
|
+
failContract("method definition must be a plain object");
|
|
396
|
+
checkMethod(definition);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function deepFreeze(value) {
|
|
400
|
+
if (Array.isArray(value)) {
|
|
401
|
+
for (const item of value)
|
|
402
|
+
deepFreeze(item);
|
|
403
|
+
return Object.freeze(value);
|
|
404
|
+
}
|
|
405
|
+
if (isPlainObject(value)) {
|
|
406
|
+
for (const key of Object.keys(value))
|
|
407
|
+
deepFreeze(value[key]);
|
|
408
|
+
return Object.freeze(value);
|
|
409
|
+
}
|
|
410
|
+
return value;
|
|
411
|
+
}
|
|
412
|
+
function defineRpcService(contract) {
|
|
413
|
+
const pure = snapshotPureInput(contract);
|
|
414
|
+
checkContract(pure);
|
|
415
|
+
return deepFreeze(pure);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// packages/core/src/plugin-services/rpc-runtime.ts
|
|
419
|
+
var PROCESSES = new Set(["control", "worker", "ingress"]);
|
|
420
|
+
var SCOPES = new Set(["global", "binding"]);
|
|
421
|
+
var PURPOSES = new Set(["bootstrap", "background", "management", "request", "attempt"]);
|
|
422
|
+
var ENDPOINTS = new WeakMap;
|
|
423
|
+
var REVOKED = new WeakMap;
|
|
424
|
+
|
|
425
|
+
// packages/core/src/plugin-services/host-rpc.ts
|
|
426
|
+
var SERVICE_ERROR_MESSAGES = {
|
|
427
|
+
unauthorized: "RPC call is not authorized",
|
|
428
|
+
not_ready: "RPC endpoint is not ready",
|
|
429
|
+
retired: "RPC endpoint is retiring",
|
|
430
|
+
revoked: "RPC endpoint generation was revoked",
|
|
431
|
+
unsupported_method: "RPC method is not published",
|
|
432
|
+
wrong_purpose: "RPC method is not published for this purpose",
|
|
433
|
+
invalid_input: "RPC input is not valid",
|
|
434
|
+
invalid_output: "RPC output is not valid",
|
|
435
|
+
invalid_operation_id: "RPC command operation id is invalid",
|
|
436
|
+
overloaded: "RPC runtime is at capacity",
|
|
437
|
+
cancelled: "RPC call was cancelled",
|
|
438
|
+
timeout: "RPC call deadline exceeded",
|
|
439
|
+
unknown: "RPC command outcome is unknown",
|
|
440
|
+
capability_unavailable: "RPC command execution is unavailable",
|
|
441
|
+
closed: "RPC runtime is closed",
|
|
442
|
+
failed: "RPC call failed",
|
|
443
|
+
conflict: "RPC command identity conflicts with an existing record",
|
|
444
|
+
expired: "RPC command result has expired",
|
|
445
|
+
pending: "RPC command is still pending",
|
|
446
|
+
rejected: "RPC command was rejected",
|
|
447
|
+
missing: "RPC command has no stored result",
|
|
448
|
+
storage_failure: "RPC command storage failed",
|
|
449
|
+
deadlock: "RPC call chain would deadlock",
|
|
450
|
+
call_depth_exceeded: "RPC call depth exceeded",
|
|
451
|
+
undeclared: "RPC service is not declared by the manifest",
|
|
452
|
+
unavailable: "RPC service is unavailable",
|
|
453
|
+
ambiguous_placement: "RPC service placement is ambiguous",
|
|
454
|
+
invalid_registration: "RPC registration is invalid"
|
|
455
|
+
};
|
|
456
|
+
var SERVICE_ERROR_CODES = new Set(Object.keys(SERVICE_ERROR_MESSAGES));
|
|
457
|
+
var EMPTY_OPTIONS = Object.freeze({});
|
|
458
|
+
var FRAME_STORE = new AsyncLocalStorage;
|
|
459
|
+
var CALLEE_INFO = new WeakMap;
|
|
460
|
+
// packages/core/src/plugin-services/peer-channel-protocol.ts
|
|
461
|
+
var CHANNEL_REQUEST_MAX_BYTES = 4 * 1024;
|
|
462
|
+
var CHANNEL_RESULT_MAX_BYTES = 60 * 1024;
|
|
463
|
+
var CHANNEL_MAX_CHUNK_BYTES = 60 * 1024;
|
|
464
|
+
var PLUGIN_CHANNEL_LANES = Object.freeze(["event", "snapshot", "stream"]);
|
|
465
|
+
var ERROR_CODES = new Set([
|
|
466
|
+
"unavailable",
|
|
467
|
+
"ambiguous",
|
|
468
|
+
"unauthorized",
|
|
469
|
+
"invalid",
|
|
470
|
+
"overloaded",
|
|
471
|
+
"expired",
|
|
472
|
+
"conflict",
|
|
473
|
+
"gap",
|
|
474
|
+
"truncated",
|
|
475
|
+
"rejected",
|
|
476
|
+
"cancelled",
|
|
477
|
+
"closed",
|
|
478
|
+
"unknown",
|
|
479
|
+
"failed"
|
|
480
|
+
]);
|
|
481
|
+
var RPC_CALL_PURPOSES2 = new Set(["bootstrap", "background", "management", "request", "attempt"]);
|
|
482
|
+
|
|
483
|
+
// packages/core/src/plugin-services/peer-channel-hub.ts
|
|
484
|
+
var CHANNEL_MAX_TRANSFER_BUFFER_BYTES = 4 * CHANNEL_MAX_CHUNK_BYTES;
|
|
485
|
+
var CHANNEL_MAX_TOTAL_BUFFER_BYTES = 16 * CHANNEL_MAX_CHUNK_BYTES;
|
|
486
|
+
var CHANNEL_STREAM_MAX_TOTAL_BYTES = 64 * 1024 * 1024;
|
|
487
|
+
var CHANNEL_STREAM_MAX_CREDIT_BYTES = 8 * CHANNEL_MAX_CHUNK_BYTES;
|
|
488
|
+
var CHANNEL_STREAM_DEFAULT_CREDIT_BYTES = 4 * CHANNEL_MAX_CHUNK_BYTES;
|
|
489
|
+
var CHANNEL_SNAPSHOT_MAX_BYTES = 64 * 1024 * 1024;
|
|
490
|
+
var CHANNEL_EVENT_MAX_PAYLOAD_BYTES = 60 * 1024;
|
|
491
|
+
var EMPTY = new Uint8Array(0);
|
|
492
|
+
var HANDLE_REGISTRATIONS = new WeakMap;
|
|
493
|
+
|
|
494
|
+
// packages/core/src/plugin-services/snapshot-store.ts
|
|
495
|
+
var DEFAULT_CHUNK_BYTES = 60 * 1024;
|
|
496
|
+
var SNAPSHOT_FAMILY_REFS = new Map;
|
|
497
|
+
|
|
498
|
+
// packages/core/src/plugin-services.ts
|
|
499
|
+
var CHANNEL_KINDS = new Set(["events", "snapshot", "stream"]);
|
|
500
|
+
|
|
501
|
+
// plugins/token-budget/server/rpc.ts
|
|
502
|
+
var command = { deduplication: "none", resultRetentionMs: null, quotaBytes: 65536, maxResultBytes: 65536 };
|
|
503
|
+
var businessRpc = defineRpcService({ id: "token-budget.admission.v1", version: 1, methods: {
|
|
504
|
+
prepare: { kind: "command", input: { type: "object", properties: { snapshot: { type: "json" } } }, output: { type: "json" }, purposes: ["attempt"], command, timeoutMs: 15000 },
|
|
505
|
+
status: { kind: "query", input: { type: "null" }, output: { type: "json" }, purposes: ["attempt"], timeoutMs: 15000 },
|
|
506
|
+
cancel: { kind: "command", input: { type: "object", properties: { sent: { type: "literal", value: false } } }, output: { type: "json" }, purposes: ["attempt"], command, timeoutMs: 15000 },
|
|
507
|
+
settle: { kind: "command", input: { type: "object", properties: { result: { type: "json" }, costNanoUsd: { type: "union", variants: [{ type: "null" }, { type: "number", integer: true, minimum: 0 }] } }, optional: ["costNanoUsd"] }, output: { type: "json" }, purposes: ["attempt"], command, timeoutMs: 15000 }
|
|
508
|
+
} });
|
|
509
|
+
|
|
2
510
|
// plugins/token-budget/server/policy.ts
|
|
3
511
|
function usdToNanoUsd(value) {
|
|
4
512
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || Number(value.toFixed(6)) !== value)
|
|
@@ -402,17 +910,24 @@ function createControl(host) {
|
|
|
402
910
|
const policy = () => {
|
|
403
911
|
return publication(state);
|
|
404
912
|
};
|
|
913
|
+
if (host.services?.rpc) {
|
|
914
|
+
const handlers = Object.fromEntries(Object.keys(businessRpc.methods).map((method) => [method, (payload, context) => {
|
|
915
|
+
if (!host.runAdmissionOperation)
|
|
916
|
+
throw new Error("token-budget.host_admission_required");
|
|
917
|
+
return host.runAdmissionOperation(method, payload, host.resolveRpcCallee?.(context.callee), async (target) => {
|
|
918
|
+
const before = policy().version;
|
|
919
|
+
const result = stateRpc(method, payload, { state, requestId: target.requestId, attemptId: target.attemptId, principal: target.principal });
|
|
920
|
+
const next = policy();
|
|
921
|
+
if (next.version !== before || method === "settle")
|
|
922
|
+
await host.publishPolicy?.(next);
|
|
923
|
+
return result;
|
|
924
|
+
});
|
|
925
|
+
}]));
|
|
926
|
+
host.services.rpc.publish(businessRpc, handlers);
|
|
927
|
+
}
|
|
405
928
|
return {
|
|
406
929
|
policy,
|
|
407
930
|
rpc: [],
|
|
408
|
-
stateRpc: async (method, payload, ctx) => {
|
|
409
|
-
const before = policy().version;
|
|
410
|
-
const result = stateRpc(method, payload, ctx);
|
|
411
|
-
const next = policy();
|
|
412
|
-
if (next.version !== before || method === "settle")
|
|
413
|
-
await host.publishPolicy?.(next);
|
|
414
|
-
return result;
|
|
415
|
-
},
|
|
416
931
|
api: [{ path: "/keys/:keyId", methods: ["GET", "PUT"], handler: "keyPolicy", async invoke(ctx) {
|
|
417
932
|
const keyId = decodeURIComponent(new URL(ctx.request.url).pathname.split("/").pop() ?? "");
|
|
418
933
|
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(keyId))
|
|
@@ -463,4 +978,4 @@ export {
|
|
|
463
978
|
recoverUsage
|
|
464
979
|
};
|
|
465
980
|
|
|
466
|
-
//# debugId=
|
|
981
|
+
//# debugId=15580272D1CF402D64756E2164756E21
|