@copilotkit/shared 1.66.4 → 1.67.1
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/README.md +53 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +149 -4
- package/dist/index.umd.js.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/utils/index.cjs +3 -2
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.cts +1 -0
- package/dist/utils/index.d.cts.map +1 -1
- package/dist/utils/index.d.mts +1 -0
- package/dist/utils/index.d.mts.map +1 -1
- package/dist/utils/index.mjs +3 -2
- package/dist/utils/index.mjs.map +1 -1
- package/dist/utils/inspector-metadata.cjs +147 -0
- package/dist/utils/inspector-metadata.cjs.map +1 -0
- package/dist/utils/inspector-metadata.d.cts +55 -0
- package/dist/utils/inspector-metadata.d.cts.map +1 -0
- package/dist/utils/inspector-metadata.d.mts +55 -0
- package/dist/utils/inspector-metadata.d.mts.map +1 -0
- package/dist/utils/inspector-metadata.mjs +146 -0
- package/dist/utils/inspector-metadata.mjs.map +1 -0
- package/dist/utils/types.cjs.map +1 -1
- package/dist/utils/types.d.cts +2 -0
- package/dist/utils/types.d.cts.map +1 -1
- package/dist/utils/types.d.mts +2 -0
- package/dist/utils/types.d.mts.map +1 -1
- package/dist/utils/types.mjs.map +1 -1
- package/package.json +1 -1
- package/src/utils/index.ts +3 -2
- package/src/utils/inspector-metadata.test.ts +742 -0
- package/src/utils/inspector-metadata.ts +273 -0
- package/src/utils/types.ts +2 -0
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { parseInspectorMetadataV1 } from "./inspector-metadata";
|
|
4
|
+
|
|
5
|
+
function metadataWithUsage(usage: unknown): Record<string, unknown> {
|
|
6
|
+
return {
|
|
7
|
+
schemaVersion: 1,
|
|
8
|
+
identity: {
|
|
9
|
+
organizationName: "Acme",
|
|
10
|
+
projectName: "Support",
|
|
11
|
+
},
|
|
12
|
+
usage,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function validUsage(limit: unknown = { kind: "finite", value: 100 }) {
|
|
17
|
+
return {
|
|
18
|
+
used: 12,
|
|
19
|
+
limit,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function nullPrototypeRecord(
|
|
24
|
+
fields: Record<string, unknown>,
|
|
25
|
+
): Record<string, unknown> {
|
|
26
|
+
return Object.assign(Object.create(null), fields);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test("parses complete metadata and strips unknown fields", () => {
|
|
30
|
+
const value = {
|
|
31
|
+
schemaVersion: 1,
|
|
32
|
+
identity: {
|
|
33
|
+
organizationName: " Acme Inc. ",
|
|
34
|
+
projectName: " Support ",
|
|
35
|
+
organizationId: "org-secret",
|
|
36
|
+
},
|
|
37
|
+
plan: {
|
|
38
|
+
code: " enterprise ",
|
|
39
|
+
label: " Enterprise ",
|
|
40
|
+
internalPriceId: "price-secret",
|
|
41
|
+
},
|
|
42
|
+
license: {
|
|
43
|
+
state: "valid",
|
|
44
|
+
expiresAt: "2030-01-01T00:00:00.000Z",
|
|
45
|
+
},
|
|
46
|
+
action: {
|
|
47
|
+
kind: "manage_plan",
|
|
48
|
+
url: " https://cloud.copilotkit.ai/manage ",
|
|
49
|
+
method: "POST",
|
|
50
|
+
},
|
|
51
|
+
usage: {
|
|
52
|
+
used: 12,
|
|
53
|
+
limit: {
|
|
54
|
+
kind: "finite",
|
|
55
|
+
value: 100,
|
|
56
|
+
unit: "threads",
|
|
57
|
+
},
|
|
58
|
+
internalThreadIds: ["thread-secret"],
|
|
59
|
+
},
|
|
60
|
+
internalOrganizationId: "org-secret",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const result = parseInspectorMetadataV1(value);
|
|
64
|
+
|
|
65
|
+
expect(result).toStrictEqual({
|
|
66
|
+
schemaVersion: 1,
|
|
67
|
+
identity: {
|
|
68
|
+
organizationName: "Acme Inc.",
|
|
69
|
+
projectName: "Support",
|
|
70
|
+
},
|
|
71
|
+
plan: {
|
|
72
|
+
code: "enterprise",
|
|
73
|
+
label: "Enterprise",
|
|
74
|
+
},
|
|
75
|
+
license: {
|
|
76
|
+
state: "valid",
|
|
77
|
+
},
|
|
78
|
+
action: {
|
|
79
|
+
kind: "manage_plan",
|
|
80
|
+
url: "https://cloud.copilotkit.ai/manage",
|
|
81
|
+
},
|
|
82
|
+
usage: {
|
|
83
|
+
used: 12,
|
|
84
|
+
limit: {
|
|
85
|
+
kind: "finite",
|
|
86
|
+
value: 100,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("parses identity when it is the only metadata module", () => {
|
|
93
|
+
const value = {
|
|
94
|
+
schemaVersion: 1,
|
|
95
|
+
identity: {
|
|
96
|
+
organizationName: "Acme",
|
|
97
|
+
projectName: "Support",
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const result = parseInspectorMetadataV1(value);
|
|
102
|
+
|
|
103
|
+
expect(result).toStrictEqual({
|
|
104
|
+
schemaVersion: 1,
|
|
105
|
+
identity: {
|
|
106
|
+
organizationName: "Acme",
|
|
107
|
+
projectName: "Support",
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("parses plan when it is the only metadata module", () => {
|
|
113
|
+
const value = {
|
|
114
|
+
schemaVersion: 1,
|
|
115
|
+
plan: {
|
|
116
|
+
code: "enterprise",
|
|
117
|
+
label: "Enterprise",
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const result = parseInspectorMetadataV1(value);
|
|
122
|
+
|
|
123
|
+
expect(result).toStrictEqual({
|
|
124
|
+
schemaVersion: 1,
|
|
125
|
+
plan: {
|
|
126
|
+
code: "enterprise",
|
|
127
|
+
label: "Enterprise",
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("parses license when it is the only metadata module", () => {
|
|
133
|
+
const value = {
|
|
134
|
+
schemaVersion: 1,
|
|
135
|
+
license: {
|
|
136
|
+
state: "expired",
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const result = parseInspectorMetadataV1(value);
|
|
141
|
+
|
|
142
|
+
expect(result).toStrictEqual({
|
|
143
|
+
schemaVersion: 1,
|
|
144
|
+
license: {
|
|
145
|
+
state: "expired",
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("parses action when it is the only metadata module", () => {
|
|
151
|
+
const value = {
|
|
152
|
+
schemaVersion: 1,
|
|
153
|
+
action: {
|
|
154
|
+
kind: "renew",
|
|
155
|
+
url: "https://cloud.copilotkit.ai/renew",
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const result = parseInspectorMetadataV1(value);
|
|
160
|
+
|
|
161
|
+
expect(result).toStrictEqual({
|
|
162
|
+
schemaVersion: 1,
|
|
163
|
+
action: {
|
|
164
|
+
kind: "renew",
|
|
165
|
+
url: "https://cloud.copilotkit.ai/renew",
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("parses usage when it is the only metadata module", () => {
|
|
171
|
+
const value = {
|
|
172
|
+
schemaVersion: 1,
|
|
173
|
+
usage: validUsage(),
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const result = parseInspectorMetadataV1(value);
|
|
177
|
+
|
|
178
|
+
expect(result).toStrictEqual({
|
|
179
|
+
schemaVersion: 1,
|
|
180
|
+
usage: validUsage(),
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test.each([
|
|
185
|
+
{
|
|
186
|
+
name: "identity",
|
|
187
|
+
value: {
|
|
188
|
+
schemaVersion: 1,
|
|
189
|
+
identity: { organizationName: " ", projectName: "Support" },
|
|
190
|
+
plan: { code: "developer", label: "Developer" },
|
|
191
|
+
},
|
|
192
|
+
expected: {
|
|
193
|
+
schemaVersion: 1,
|
|
194
|
+
plan: { code: "developer", label: "Developer" },
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: "plan",
|
|
199
|
+
value: {
|
|
200
|
+
schemaVersion: 1,
|
|
201
|
+
identity: { organizationName: "Acme", projectName: "Support" },
|
|
202
|
+
plan: { code: "enterprise", label: "" },
|
|
203
|
+
},
|
|
204
|
+
expected: {
|
|
205
|
+
schemaVersion: 1,
|
|
206
|
+
identity: { organizationName: "Acme", projectName: "Support" },
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: "license",
|
|
211
|
+
value: {
|
|
212
|
+
schemaVersion: 1,
|
|
213
|
+
plan: { code: "developer", label: "Developer" },
|
|
214
|
+
license: { state: "revoked" },
|
|
215
|
+
},
|
|
216
|
+
expected: {
|
|
217
|
+
schemaVersion: 1,
|
|
218
|
+
plan: { code: "developer", label: "Developer" },
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "action",
|
|
223
|
+
value: {
|
|
224
|
+
schemaVersion: 1,
|
|
225
|
+
plan: { code: "developer", label: "Developer" },
|
|
226
|
+
action: {
|
|
227
|
+
kind: "manage_plan",
|
|
228
|
+
url: "http://cloud.copilotkit.ai/manage",
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
expected: {
|
|
232
|
+
schemaVersion: 1,
|
|
233
|
+
plan: { code: "developer", label: "Developer" },
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: "usage",
|
|
238
|
+
value: {
|
|
239
|
+
schemaVersion: 1,
|
|
240
|
+
plan: { code: "developer", label: "Developer" },
|
|
241
|
+
usage: validUsage({ kind: "finite", value: 0 }),
|
|
242
|
+
},
|
|
243
|
+
expected: {
|
|
244
|
+
schemaVersion: 1,
|
|
245
|
+
plan: { code: "developer", label: "Developer" },
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
])(
|
|
249
|
+
"omits an invalid $name module while retaining valid metadata",
|
|
250
|
+
(testCase) => {
|
|
251
|
+
const result = parseInspectorMetadataV1(testCase.value);
|
|
252
|
+
|
|
253
|
+
expect(result).toStrictEqual(testCase.expected);
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
test.each([undefined, null, "metadata", 1, true, [], new Date()])(
|
|
258
|
+
"rejects non-record top-level input: %s",
|
|
259
|
+
(value) => {
|
|
260
|
+
const result = parseInspectorMetadataV1(value);
|
|
261
|
+
|
|
262
|
+
expect(result).toBeUndefined();
|
|
263
|
+
},
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
test("rejects a Date instance with an own schema version", () => {
|
|
267
|
+
const value = Object.assign(new Date(), { schemaVersion: 1 });
|
|
268
|
+
|
|
269
|
+
const result = parseInspectorMetadataV1(value);
|
|
270
|
+
|
|
271
|
+
expect(result).toBeUndefined();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("rejects a class instance with metadata fields", () => {
|
|
275
|
+
class MetadataEnvelope {
|
|
276
|
+
readonly schemaVersion = 1;
|
|
277
|
+
readonly identity = {
|
|
278
|
+
organizationName: "Acme",
|
|
279
|
+
projectName: "Support",
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const value = new MetadataEnvelope();
|
|
283
|
+
|
|
284
|
+
const result = parseInspectorMetadataV1(value);
|
|
285
|
+
|
|
286
|
+
expect(result).toBeUndefined();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("rejects a top-level object with an inherited schema version", () => {
|
|
290
|
+
const value = Object.create({
|
|
291
|
+
schemaVersion: 1,
|
|
292
|
+
identity: {
|
|
293
|
+
organizationName: "Acme",
|
|
294
|
+
projectName: "Support",
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const result = parseInspectorMetadataV1(value);
|
|
299
|
+
|
|
300
|
+
expect(result).toBeUndefined();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("omits a module whose fields come from its prototype", () => {
|
|
304
|
+
const value = {
|
|
305
|
+
schemaVersion: 1,
|
|
306
|
+
identity: Object.create({
|
|
307
|
+
organizationName: "Acme",
|
|
308
|
+
projectName: "Support",
|
|
309
|
+
}),
|
|
310
|
+
plan: {
|
|
311
|
+
code: "developer",
|
|
312
|
+
label: "Developer",
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const result = parseInspectorMetadataV1(value);
|
|
317
|
+
|
|
318
|
+
expect(result).toStrictEqual({
|
|
319
|
+
schemaVersion: 1,
|
|
320
|
+
plan: {
|
|
321
|
+
code: "developer",
|
|
322
|
+
label: "Developer",
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("accepts null-prototype metadata records", () => {
|
|
328
|
+
const identity = nullPrototypeRecord({
|
|
329
|
+
organizationName: "Acme",
|
|
330
|
+
projectName: "Support",
|
|
331
|
+
});
|
|
332
|
+
const value = nullPrototypeRecord({
|
|
333
|
+
schemaVersion: 1,
|
|
334
|
+
identity,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const result = parseInspectorMetadataV1(value);
|
|
338
|
+
|
|
339
|
+
expect(result).toStrictEqual({
|
|
340
|
+
schemaVersion: 1,
|
|
341
|
+
identity: {
|
|
342
|
+
organizationName: "Acme",
|
|
343
|
+
projectName: "Support",
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test("rejects a proxy that throws while its prototype is inspected", () => {
|
|
349
|
+
const value = new Proxy(
|
|
350
|
+
{},
|
|
351
|
+
{
|
|
352
|
+
getPrototypeOf() {
|
|
353
|
+
throw new Error("prototype unavailable");
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
const parse = () => parseInspectorMetadataV1(value);
|
|
359
|
+
|
|
360
|
+
expect(parse).not.toThrow();
|
|
361
|
+
expect(parse()).toBeUndefined();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test.each([undefined, null, 0, 2, "1", true])(
|
|
365
|
+
"rejects an unknown or missing schema version: %s",
|
|
366
|
+
(schemaVersion) => {
|
|
367
|
+
const result = parseInspectorMetadataV1({ schemaVersion });
|
|
368
|
+
|
|
369
|
+
expect(result).toBeUndefined();
|
|
370
|
+
},
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
test.each([
|
|
374
|
+
"https://cloud.copilotkit.ai/manage",
|
|
375
|
+
"https://cloud.copilotkit.ai/manage/plan",
|
|
376
|
+
"http://localhost/manage",
|
|
377
|
+
"http://localhost:3000/manage",
|
|
378
|
+
"http://127.0.0.1:3000/manage",
|
|
379
|
+
"http://[::1]:3000/manage",
|
|
380
|
+
])("accepts a safe action URL: %s", (url) => {
|
|
381
|
+
const value = {
|
|
382
|
+
schemaVersion: 1,
|
|
383
|
+
action: {
|
|
384
|
+
kind: "enable_intelligence",
|
|
385
|
+
url,
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const result = parseInspectorMetadataV1(value);
|
|
390
|
+
|
|
391
|
+
expect(result?.action).toStrictEqual({
|
|
392
|
+
kind: "enable_intelligence",
|
|
393
|
+
url,
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test.each([
|
|
398
|
+
"",
|
|
399
|
+
" ",
|
|
400
|
+
"/manage",
|
|
401
|
+
"mailto:billing@copilotkit.ai",
|
|
402
|
+
"ftp://cloud.copilotkit.ai/manage",
|
|
403
|
+
"http://cloud.copilotkit.ai/manage",
|
|
404
|
+
"http://localhost.example.com/manage",
|
|
405
|
+
"http://sub.localhost/manage",
|
|
406
|
+
"http://127.0.0.2/manage",
|
|
407
|
+
"http://[::2]/manage",
|
|
408
|
+
"http://0.0.0.0/manage",
|
|
409
|
+
"https://@cloud.copilotkit.ai/manage",
|
|
410
|
+
"https://user@cloud.copilotkit.ai/manage",
|
|
411
|
+
"https://user:password@cloud.copilotkit.ai/manage",
|
|
412
|
+
"https://cloud.copilotkit.ai/manage?source=inspector",
|
|
413
|
+
"https://cloud.copilotkit.ai/manage?",
|
|
414
|
+
"https://cloud.copilotkit.ai/manage#billing",
|
|
415
|
+
"https://cloud.copilotkit.ai/manage#",
|
|
416
|
+
])("rejects an unsafe action URL: %s", (url) => {
|
|
417
|
+
const value = {
|
|
418
|
+
schemaVersion: 1,
|
|
419
|
+
plan: {
|
|
420
|
+
code: "developer",
|
|
421
|
+
label: "Developer",
|
|
422
|
+
},
|
|
423
|
+
action: {
|
|
424
|
+
kind: "renew",
|
|
425
|
+
url,
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const result = parseInspectorMetadataV1(value);
|
|
430
|
+
|
|
431
|
+
expect(result).toStrictEqual({
|
|
432
|
+
schemaVersion: 1,
|
|
433
|
+
plan: {
|
|
434
|
+
code: "developer",
|
|
435
|
+
label: "Developer",
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test.each([
|
|
441
|
+
{ kind: "finite", value: 100 },
|
|
442
|
+
{ kind: "unlimited" },
|
|
443
|
+
{ kind: "unknown" },
|
|
444
|
+
])("parses the $kind usage limit", (limit) => {
|
|
445
|
+
const value = {
|
|
446
|
+
schemaVersion: 1,
|
|
447
|
+
usage: validUsage(limit),
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const result = parseInspectorMetadataV1(value);
|
|
451
|
+
|
|
452
|
+
expect(result?.usage?.limit).toStrictEqual(limit);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
test.each([
|
|
456
|
+
{
|
|
457
|
+
name: "finite absent",
|
|
458
|
+
limit: { kind: "finite", value: 200 },
|
|
459
|
+
expiringSoonCount: undefined,
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
name: "finite zero",
|
|
463
|
+
limit: { kind: "finite", value: 200 },
|
|
464
|
+
expiringSoonCount: 0,
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
name: "finite positive",
|
|
468
|
+
limit: { kind: "finite", value: 200 },
|
|
469
|
+
expiringSoonCount: 37,
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
name: "unlimited absent",
|
|
473
|
+
limit: { kind: "unlimited" },
|
|
474
|
+
expiringSoonCount: undefined,
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
name: "unlimited zero",
|
|
478
|
+
limit: { kind: "unlimited" },
|
|
479
|
+
expiringSoonCount: 0,
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
name: "unlimited positive",
|
|
483
|
+
limit: { kind: "unlimited" },
|
|
484
|
+
expiringSoonCount: 37,
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
name: "unknown absent",
|
|
488
|
+
limit: { kind: "unknown" },
|
|
489
|
+
expiringSoonCount: undefined,
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
name: "unknown zero",
|
|
493
|
+
limit: { kind: "unknown" },
|
|
494
|
+
expiringSoonCount: 0,
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
name: "unknown positive",
|
|
498
|
+
limit: { kind: "unknown" },
|
|
499
|
+
expiringSoonCount: 37,
|
|
500
|
+
},
|
|
501
|
+
])("parses $name expiring-soon usage", ({ limit, expiringSoonCount }) => {
|
|
502
|
+
const expiry = expiringSoonCount === undefined ? {} : { expiringSoonCount };
|
|
503
|
+
const value = {
|
|
504
|
+
schemaVersion: 1,
|
|
505
|
+
usage: { used: 148, limit, ...expiry },
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
const result = parseInspectorMetadataV1(value);
|
|
509
|
+
|
|
510
|
+
expect(result).toStrictEqual({
|
|
511
|
+
schemaVersion: 1,
|
|
512
|
+
usage: { used: 148, limit, ...expiry },
|
|
513
|
+
});
|
|
514
|
+
expect(
|
|
515
|
+
Object.prototype.hasOwnProperty.call(
|
|
516
|
+
result?.usage ?? {},
|
|
517
|
+
"expiringSoonCount",
|
|
518
|
+
),
|
|
519
|
+
).toBe(expiringSoonCount !== undefined);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
test("parses the maximum safe expiring-soon count", () => {
|
|
523
|
+
const value = {
|
|
524
|
+
schemaVersion: 1,
|
|
525
|
+
usage: {
|
|
526
|
+
used: 148,
|
|
527
|
+
limit: { kind: "finite", value: 200 },
|
|
528
|
+
expiringSoonCount: Number.MAX_SAFE_INTEGER,
|
|
529
|
+
},
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
const result = parseInspectorMetadataV1(value);
|
|
533
|
+
|
|
534
|
+
expect(result?.usage).toStrictEqual({
|
|
535
|
+
used: 148,
|
|
536
|
+
limit: { kind: "finite", value: 200 },
|
|
537
|
+
expiringSoonCount: Number.MAX_SAFE_INTEGER,
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
test.each([
|
|
542
|
+
{ name: "negative", expiringSoonCount: -1 },
|
|
543
|
+
{ name: "fractional", expiringSoonCount: 1.5 },
|
|
544
|
+
{
|
|
545
|
+
name: "unsafe",
|
|
546
|
+
expiringSoonCount: Number.MAX_SAFE_INTEGER + 1,
|
|
547
|
+
},
|
|
548
|
+
{ name: "string", expiringSoonCount: "37" },
|
|
549
|
+
{ name: "NaN", expiringSoonCount: Number.NaN },
|
|
550
|
+
{ name: "infinite", expiringSoonCount: Number.POSITIVE_INFINITY },
|
|
551
|
+
])(
|
|
552
|
+
"drops a $name expiring-soon leaf without dropping usage",
|
|
553
|
+
({ expiringSoonCount }) => {
|
|
554
|
+
const value = {
|
|
555
|
+
schemaVersion: 1,
|
|
556
|
+
usage: {
|
|
557
|
+
used: 148,
|
|
558
|
+
limit: { kind: "finite", value: 200 },
|
|
559
|
+
expiringSoonCount,
|
|
560
|
+
},
|
|
561
|
+
plan: { code: "free", label: "Free" },
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
const result = parseInspectorMetadataV1(value);
|
|
565
|
+
|
|
566
|
+
expect(result).toStrictEqual({
|
|
567
|
+
schemaVersion: 1,
|
|
568
|
+
usage: {
|
|
569
|
+
used: 148,
|
|
570
|
+
limit: { kind: "finite", value: 200 },
|
|
571
|
+
},
|
|
572
|
+
plan: { code: "free", label: "Free" },
|
|
573
|
+
});
|
|
574
|
+
},
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
test("ignores an inherited expiring-soon count", () => {
|
|
578
|
+
const prototype = { expiringSoonCount: 37 };
|
|
579
|
+
const target = Object.assign(
|
|
580
|
+
Object.create(prototype),
|
|
581
|
+
validUsage({ kind: "finite", value: 200 }),
|
|
582
|
+
);
|
|
583
|
+
const usage = new Proxy(target, {
|
|
584
|
+
getPrototypeOf() {
|
|
585
|
+
return Object.prototype;
|
|
586
|
+
},
|
|
587
|
+
});
|
|
588
|
+
const value = { schemaVersion: 1, usage };
|
|
589
|
+
|
|
590
|
+
const result = parseInspectorMetadataV1(value);
|
|
591
|
+
|
|
592
|
+
expect(result?.usage).toStrictEqual(
|
|
593
|
+
validUsage({ kind: "finite", value: 200 }),
|
|
594
|
+
);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
test("does not invoke an expiring-soon getter", () => {
|
|
598
|
+
const readExpiringSoonCount = vi.fn(() => 37);
|
|
599
|
+
const usage = validUsage({ kind: "finite", value: 200 });
|
|
600
|
+
Object.defineProperty(usage, "expiringSoonCount", {
|
|
601
|
+
enumerable: true,
|
|
602
|
+
get: readExpiringSoonCount,
|
|
603
|
+
});
|
|
604
|
+
const value = { schemaVersion: 1, usage };
|
|
605
|
+
|
|
606
|
+
const result = parseInspectorMetadataV1(value);
|
|
607
|
+
|
|
608
|
+
expect(result?.usage).toStrictEqual(
|
|
609
|
+
validUsage({ kind: "finite", value: 200 }),
|
|
610
|
+
);
|
|
611
|
+
expect(readExpiringSoonCount).not.toHaveBeenCalled();
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
test("keeps usage when the expiring-soon descriptor cannot be read", () => {
|
|
615
|
+
const usage = new Proxy(validUsage({ kind: "finite", value: 200 }), {
|
|
616
|
+
getOwnPropertyDescriptor(target, property) {
|
|
617
|
+
if (property === "expiringSoonCount") {
|
|
618
|
+
throw new Error("descriptor unavailable");
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return Reflect.getOwnPropertyDescriptor(target, property);
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
const value = { schemaVersion: 1, usage };
|
|
625
|
+
|
|
626
|
+
const result = parseInspectorMetadataV1(value);
|
|
627
|
+
|
|
628
|
+
expect(result?.usage).toStrictEqual(
|
|
629
|
+
validUsage({ kind: "finite", value: 200 }),
|
|
630
|
+
);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
test("returns validated usage when expiry descriptor work mutates input", () => {
|
|
634
|
+
const usage = new Proxy(validUsage({ kind: "finite", value: 200 }), {
|
|
635
|
+
getOwnPropertyDescriptor(target, property) {
|
|
636
|
+
if (property === "expiringSoonCount") {
|
|
637
|
+
target.used = -1;
|
|
638
|
+
throw new Error("descriptor unavailable");
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return Reflect.getOwnPropertyDescriptor(target, property);
|
|
642
|
+
},
|
|
643
|
+
});
|
|
644
|
+
const value = { schemaVersion: 1, usage };
|
|
645
|
+
|
|
646
|
+
const result = parseInspectorMetadataV1(value);
|
|
647
|
+
|
|
648
|
+
expect(result?.usage).toStrictEqual(
|
|
649
|
+
validUsage({ kind: "finite", value: 200 }),
|
|
650
|
+
);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
test.each([
|
|
654
|
+
-1,
|
|
655
|
+
0.5,
|
|
656
|
+
Number.NaN,
|
|
657
|
+
Number.POSITIVE_INFINITY,
|
|
658
|
+
Number.MAX_SAFE_INTEGER + 1,
|
|
659
|
+
"12",
|
|
660
|
+
null,
|
|
661
|
+
])("omits usage when used is not a finite nonnegative integer: %s", (used) => {
|
|
662
|
+
const value = metadataWithUsage({
|
|
663
|
+
...validUsage(),
|
|
664
|
+
used,
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
const result = parseInspectorMetadataV1(value);
|
|
668
|
+
|
|
669
|
+
expect(result).toStrictEqual({
|
|
670
|
+
schemaVersion: 1,
|
|
671
|
+
identity: {
|
|
672
|
+
organizationName: "Acme",
|
|
673
|
+
projectName: "Support",
|
|
674
|
+
},
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
test.each([
|
|
679
|
+
0,
|
|
680
|
+
-1,
|
|
681
|
+
1.5,
|
|
682
|
+
Number.NaN,
|
|
683
|
+
Number.POSITIVE_INFINITY,
|
|
684
|
+
Number.MAX_SAFE_INTEGER + 1,
|
|
685
|
+
"100",
|
|
686
|
+
null,
|
|
687
|
+
])("omits usage when a finite limit value is invalid: %s", (limitValue) => {
|
|
688
|
+
const value = metadataWithUsage(
|
|
689
|
+
validUsage({
|
|
690
|
+
kind: "finite",
|
|
691
|
+
value: limitValue,
|
|
692
|
+
}),
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
const result = parseInspectorMetadataV1(value);
|
|
696
|
+
|
|
697
|
+
expect(result).toStrictEqual({
|
|
698
|
+
schemaVersion: 1,
|
|
699
|
+
identity: {
|
|
700
|
+
organizationName: "Acme",
|
|
701
|
+
projectName: "Support",
|
|
702
|
+
},
|
|
703
|
+
});
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
test.each([
|
|
707
|
+
{ kind: "finite" },
|
|
708
|
+
{ kind: "unlimited", value: 100 },
|
|
709
|
+
{ kind: "unknown", reason: "hidden" },
|
|
710
|
+
{ kind: "metered" },
|
|
711
|
+
])("normalizes or rejects the usage limit shape: $kind", (limit) => {
|
|
712
|
+
const value = {
|
|
713
|
+
schemaVersion: 1,
|
|
714
|
+
usage: validUsage(limit),
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
const result = parseInspectorMetadataV1(value);
|
|
718
|
+
|
|
719
|
+
if (limit.kind === "finite" || limit.kind === "metered") {
|
|
720
|
+
expect(result).toStrictEqual({ schemaVersion: 1 });
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
expect(result?.usage?.limit).toStrictEqual({ kind: limit.kind });
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
test.each(["", " ", "manage-plan", null, 1])(
|
|
728
|
+
"omits an action with an unsupported kind: %s",
|
|
729
|
+
(kind) => {
|
|
730
|
+
const value = {
|
|
731
|
+
schemaVersion: 1,
|
|
732
|
+
action: {
|
|
733
|
+
kind,
|
|
734
|
+
url: "https://cloud.copilotkit.ai/manage",
|
|
735
|
+
},
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
const result = parseInspectorMetadataV1(value);
|
|
739
|
+
|
|
740
|
+
expect(result).toStrictEqual({ schemaVersion: 1 });
|
|
741
|
+
},
|
|
742
|
+
);
|