@copilotkit/shared 1.69.3 → 1.70.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/index.cjs +31 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -17
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +19 -17
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +29 -15
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +159 -31
- package/dist/index.umd.js.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/telemetry/index.d.mts +3 -2
- package/dist/telemetry/lambda-client.cjs +25 -4
- package/dist/telemetry/lambda-client.cjs.map +1 -1
- package/dist/telemetry/lambda-client.d.cts +14 -1
- package/dist/telemetry/lambda-client.d.cts.map +1 -1
- package/dist/telemetry/lambda-client.d.mts +14 -1
- package/dist/telemetry/lambda-client.d.mts.map +1 -1
- package/dist/telemetry/lambda-client.mjs +25 -5
- package/dist/telemetry/lambda-client.mjs.map +1 -1
- package/dist/telemetry/sampling.cjs +28 -0
- package/dist/telemetry/sampling.cjs.map +1 -0
- package/dist/telemetry/sampling.d.cts +37 -0
- package/dist/telemetry/sampling.d.cts.map +1 -0
- package/dist/telemetry/sampling.d.mts +37 -0
- package/dist/telemetry/sampling.d.mts.map +1 -0
- package/dist/telemetry/sampling.mjs +25 -0
- package/dist/telemetry/sampling.mjs.map +1 -0
- package/dist/telemetry/telemetry-client.cjs +72 -11
- package/dist/telemetry/telemetry-client.cjs.map +1 -1
- package/dist/telemetry/telemetry-client.d.cts +43 -1
- package/dist/telemetry/telemetry-client.d.cts.map +1 -1
- package/dist/telemetry/telemetry-client.d.mts +43 -1
- package/dist/telemetry/telemetry-client.d.mts.map +1 -1
- package/dist/telemetry/telemetry-client.mjs +73 -12
- package/dist/telemetry/telemetry-client.mjs.map +1 -1
- package/dist/utils/index.d.cts +1 -1
- package/dist/utils/index.d.mts +1 -1
- package/dist/utils/types.cjs.map +1 -1
- package/dist/utils/types.d.cts +46 -1
- package/dist/utils/types.d.cts.map +1 -1
- package/dist/utils/types.d.mts +46 -1
- package/dist/utils/types.d.mts.map +1 -1
- package/dist/utils/types.mjs.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/license-context.test.ts +224 -25
- package/src/index.ts +72 -16
- package/src/telemetry/index.ts +2 -0
- package/src/telemetry/lambda-client.test.ts +336 -1
- package/src/telemetry/lambda-client.ts +56 -15
- package/src/telemetry/sampling.test.ts +65 -0
- package/src/telemetry/sampling.ts +70 -0
- package/src/telemetry/telemetry-blank-license-identity.test.ts +121 -0
- package/src/telemetry/telemetry-client.test.ts +438 -15
- package/src/telemetry/telemetry-client.ts +145 -30
- package/src/utils/__tests__/conditions.test.ts +161 -0
- package/src/utils/types.ts +52 -0
|
@@ -1,33 +1,232 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
2
|
import { createLicenseContextValue } from "../index";
|
|
3
3
|
import type { RuntimeLicenseStatus } from "../utils/types";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
test("license context fails open when no status is known", () => {
|
|
6
|
+
for (const status of [null, undefined] as const) {
|
|
7
|
+
const ctx = createLicenseContextValue(status);
|
|
8
|
+
expect(ctx.status).toBeNull();
|
|
9
|
+
expect(ctx.license).toBeNull();
|
|
10
|
+
expect(ctx.checkFeature("chat")).toBe(true);
|
|
11
|
+
expect(ctx.getLimit("chat")).toBeNull();
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test.each(["valid", "none", "expiring", "unknown"] as RuntimeLicenseStatus[])(
|
|
16
|
+
"license context enables features for %s status",
|
|
17
|
+
(status) => {
|
|
18
|
+
const ctx = createLicenseContextValue(status);
|
|
19
|
+
expect(ctx.status).toBe(status);
|
|
20
|
+
expect(ctx.checkFeature("chat")).toBe(true);
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
test.each(["expired", "invalid"] as RuntimeLicenseStatus[])(
|
|
25
|
+
"license context disables features for %s status",
|
|
26
|
+
(status) => {
|
|
27
|
+
const ctx = createLicenseContextValue(status);
|
|
28
|
+
expect(ctx.status).toBe(status);
|
|
29
|
+
expect(ctx.checkFeature("chat")).toBe(false);
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
test("license context uses ready active feature grants and numeric limits", () => {
|
|
34
|
+
const ctx = createLicenseContextValue("valid", {
|
|
35
|
+
status: "ready",
|
|
36
|
+
entitlement: {
|
|
37
|
+
active: true,
|
|
38
|
+
source: "managedOrgSubscription",
|
|
39
|
+
features: {
|
|
40
|
+
chat: true,
|
|
41
|
+
threads: false,
|
|
42
|
+
},
|
|
43
|
+
limits: {
|
|
44
|
+
threads: 25,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
14
47
|
});
|
|
15
48
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
49
|
+
expect(ctx.checkFeature("chat")).toBe(true);
|
|
50
|
+
expect(ctx.checkFeature("threads")).toBe(false);
|
|
51
|
+
expect(ctx.checkFeature("unknown")).toBe(false);
|
|
52
|
+
expect(ctx.getLimit("threads")).toBe(25);
|
|
53
|
+
expect(ctx.getLimit("unknown")).toBeNull();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test.each([0, 25])(
|
|
57
|
+
"license context treats an own threads.max_count limit of %s as a thread grant",
|
|
58
|
+
(threadLimit) => {
|
|
59
|
+
const ctx = createLicenseContextValue("valid", {
|
|
60
|
+
status: "ready",
|
|
61
|
+
entitlement: {
|
|
62
|
+
active: true,
|
|
63
|
+
source: "managedOrgSubscription",
|
|
64
|
+
features: {},
|
|
65
|
+
limits: { "threads.max_count": threadLimit },
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(ctx.checkFeature("threads")).toBe(true);
|
|
70
|
+
expect(ctx.getLimit("threads.max_count")).toBe(threadLimit);
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
test.each([
|
|
75
|
+
{
|
|
76
|
+
source: "managedOrgSubscription" as const,
|
|
77
|
+
features: {},
|
|
78
|
+
limits: {
|
|
79
|
+
"threads.retention_hours": 72,
|
|
80
|
+
"threads.max_count": 200,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
source: "selfHostedDeploymentLicense" as const,
|
|
85
|
+
features: {
|
|
86
|
+
deployment_via_helm_chart: true,
|
|
87
|
+
msteams: true,
|
|
88
|
+
},
|
|
89
|
+
limits: {
|
|
90
|
+
"threads.retention_hours": 336,
|
|
91
|
+
"threads.max_count": 25000,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
])(
|
|
95
|
+
"license context preserves legacy UI features for an active $source payload",
|
|
96
|
+
(entitlement) => {
|
|
97
|
+
const ctx = createLicenseContextValue("valid", {
|
|
98
|
+
status: "ready",
|
|
99
|
+
entitlement: {
|
|
100
|
+
active: true,
|
|
101
|
+
...entitlement,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
for (const feature of ["chat", "popup", "sidebar", "threads"]) {
|
|
106
|
+
expect(ctx.checkFeature(feature)).toBe(true);
|
|
107
|
+
}
|
|
108
|
+
expect(ctx.checkFeature("unknown")).toBe(false);
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
test.each(["chat", "popup", "sidebar"])(
|
|
113
|
+
"license context preserves an explicit %s denial",
|
|
114
|
+
(feature) => {
|
|
115
|
+
const ctx = createLicenseContextValue("valid", {
|
|
116
|
+
status: "ready",
|
|
117
|
+
entitlement: {
|
|
118
|
+
active: true,
|
|
119
|
+
source: "managedOrgSubscription",
|
|
120
|
+
features: { [feature]: false },
|
|
121
|
+
limits: {},
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(ctx.checkFeature(feature)).toBe(false);
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
test.each([true, false])(
|
|
130
|
+
"license context preserves a legacy threads feature value of %s",
|
|
131
|
+
(threads) => {
|
|
132
|
+
const ctx = createLicenseContextValue("valid", {
|
|
133
|
+
status: "ready",
|
|
134
|
+
entitlement: {
|
|
135
|
+
active: true,
|
|
136
|
+
source: "selfHostedDeploymentLicense",
|
|
137
|
+
features: { threads },
|
|
138
|
+
limits: {},
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(ctx.checkFeature("threads")).toBe(threads);
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
test("license context denies features and limits for a ready inactive entitlement", () => {
|
|
147
|
+
const ctx = createLicenseContextValue("none", {
|
|
148
|
+
status: "ready",
|
|
149
|
+
entitlement: {
|
|
150
|
+
active: false,
|
|
151
|
+
source: "managedOrgSubscription",
|
|
152
|
+
features: {
|
|
153
|
+
chat: true,
|
|
154
|
+
},
|
|
155
|
+
limits: {
|
|
156
|
+
threads: 25,
|
|
157
|
+
},
|
|
22
158
|
},
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(ctx.checkFeature("chat")).toBe(false);
|
|
162
|
+
expect(ctx.getLimit("threads")).toBeNull();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test.each(["valid", "expiring"] as RuntimeLicenseStatus[])(
|
|
166
|
+
"license context preserves a %s legacy fallback for an inactive self-hosted entitlement",
|
|
167
|
+
(status) => {
|
|
168
|
+
const ctx = createLicenseContextValue(status, {
|
|
169
|
+
status: "ready",
|
|
170
|
+
entitlement: {
|
|
171
|
+
active: false,
|
|
172
|
+
source: "selfHostedDeploymentLicense",
|
|
173
|
+
features: {
|
|
174
|
+
threads: false,
|
|
175
|
+
},
|
|
176
|
+
limits: {
|
|
177
|
+
threads: 0,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(ctx.status).toBe(status);
|
|
183
|
+
expect(ctx.checkFeature("threads")).toBe(true);
|
|
184
|
+
expect(ctx.getLimit("threads")).toBeNull();
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
test.each([
|
|
189
|
+
{ status: undefined, expectedStatus: "none" },
|
|
190
|
+
{ status: "none", expectedStatus: "none" },
|
|
191
|
+
{ status: "expired", expectedStatus: "expired" },
|
|
192
|
+
{ status: "invalid", expectedStatus: "invalid" },
|
|
193
|
+
{ status: "unknown", expectedStatus: "unknown" },
|
|
194
|
+
] as const)(
|
|
195
|
+
"license context denies an inactive self-hosted entitlement with $status legacy status",
|
|
196
|
+
({ status, expectedStatus }) => {
|
|
197
|
+
const ctx = createLicenseContextValue(status, {
|
|
198
|
+
status: "ready",
|
|
199
|
+
entitlement: {
|
|
200
|
+
active: false,
|
|
201
|
+
source: "selfHostedDeploymentLicense",
|
|
202
|
+
features: {
|
|
203
|
+
threads: true,
|
|
204
|
+
},
|
|
205
|
+
limits: {
|
|
206
|
+
threads: 25,
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(ctx.status).toBe(expectedStatus);
|
|
212
|
+
expect(ctx.checkFeature("threads")).toBe(false);
|
|
213
|
+
expect(ctx.getLimit("threads")).toBeNull();
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
test("license context ignores inherited feature and limit keys", () => {
|
|
218
|
+
const ctx = createLicenseContextValue("valid", {
|
|
219
|
+
status: "ready",
|
|
220
|
+
entitlement: {
|
|
221
|
+
active: true,
|
|
222
|
+
source: "managedOrgSubscription",
|
|
223
|
+
features: {},
|
|
224
|
+
limits: {},
|
|
31
225
|
},
|
|
32
|
-
);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
for (const feature of ["toString", "constructor"]) {
|
|
229
|
+
expect(ctx.checkFeature(feature)).toBe(false);
|
|
230
|
+
expect(ctx.getLimit(feature)).toBeNull();
|
|
231
|
+
}
|
|
33
232
|
});
|
package/src/index.ts
CHANGED
|
@@ -33,7 +33,10 @@ export type {
|
|
|
33
33
|
} from "@copilotkit/license-verifier";
|
|
34
34
|
|
|
35
35
|
import type { LicensePayload } from "@copilotkit/license-verifier";
|
|
36
|
-
import type {
|
|
36
|
+
import type {
|
|
37
|
+
RuntimeEntitlementResponse,
|
|
38
|
+
RuntimeLicenseStatus,
|
|
39
|
+
} from "./utils/types";
|
|
37
40
|
|
|
38
41
|
// LicenseContextValue was dropped from license-verifier's public API in
|
|
39
42
|
// 0.3.0, so it is defined here. The context shape is owned by this package
|
|
@@ -44,40 +47,93 @@ import type { RuntimeLicenseStatus } from "./utils/types";
|
|
|
44
47
|
* Frontend providers create their own context using this shape.
|
|
45
48
|
*/
|
|
46
49
|
export interface LicenseContextValue {
|
|
47
|
-
/**
|
|
50
|
+
/** Effective license status after structured entitlement precedence. Null until known. */
|
|
48
51
|
status: RuntimeLicenseStatus | null;
|
|
49
52
|
/** The license payload if available. Always null on the client; the payload stays server-side. */
|
|
50
53
|
license: LicensePayload | null;
|
|
51
|
-
/** Whether a
|
|
54
|
+
/** Whether a feature is licensed. Ready entitlements override legacy status behavior. */
|
|
52
55
|
checkFeature: (feature: string) => boolean;
|
|
53
|
-
/** Get a numeric feature limit.
|
|
56
|
+
/** Get a numeric feature limit. Zero means unlimited; null means not applicable. */
|
|
54
57
|
getLimit: (feature: string) => number | null;
|
|
55
58
|
}
|
|
56
59
|
|
|
60
|
+
/** Read a record value without traversing its prototype chain. */
|
|
61
|
+
function getOwnRecordValue<Value>(
|
|
62
|
+
record: Readonly<Record<string, Value>>,
|
|
63
|
+
key: string,
|
|
64
|
+
): Value | undefined {
|
|
65
|
+
return Object.prototype.hasOwnProperty.call(record, key)
|
|
66
|
+
? record[key]
|
|
67
|
+
: undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Legacy UI surfaces that remain available for every active entitlement. */
|
|
71
|
+
function isLegacyUiFeature(feature: string): boolean {
|
|
72
|
+
return feature === "chat" || feature === "popup" || feature === "sidebar";
|
|
73
|
+
}
|
|
74
|
+
|
|
57
75
|
/**
|
|
58
|
-
* Client-safe license context factory, driven by the license
|
|
76
|
+
* Client-safe license context factory, driven by the license authority the
|
|
59
77
|
* runtime reports via /info.
|
|
60
78
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
79
|
+
* A ready managed entitlement is authoritative in both directions. A ready
|
|
80
|
+
* active self-hosted entitlement is also authoritative, while an inactive
|
|
81
|
+
* self-hosted response preserves the legacy signed-license fallback. Active
|
|
82
|
+
* entitlements supply feature grants and limits; authoritative inactive
|
|
83
|
+
* entitlements deny every feature and limit. Older runtimes that report only a
|
|
84
|
+
* status retain the legacy behavior: features are enabled unless the status is
|
|
85
|
+
* "expired" or "invalid", and no limits are reported. This is inlined here to
|
|
86
|
+
* avoid importing the full license-verifier bundle (which depends on Node's
|
|
87
|
+
* `crypto`) into browser bundles.
|
|
69
88
|
*/
|
|
70
89
|
export function createLicenseContextValue(
|
|
71
90
|
status: RuntimeLicenseStatus | null | undefined,
|
|
91
|
+
runtimeEntitlements?: RuntimeEntitlementResponse,
|
|
72
92
|
): LicenseContextValue {
|
|
73
|
-
const
|
|
93
|
+
const readyEntitlement =
|
|
94
|
+
runtimeEntitlements?.status === "ready"
|
|
95
|
+
? runtimeEntitlements.entitlement
|
|
96
|
+
: null;
|
|
97
|
+
const hasUsableSelfHostedLegacyFallback =
|
|
98
|
+
readyEntitlement &&
|
|
99
|
+
!readyEntitlement.active &&
|
|
100
|
+
readyEntitlement.source === "selfHostedDeploymentLicense" &&
|
|
101
|
+
(status === "valid" || status === "expiring");
|
|
102
|
+
const featureAuthority =
|
|
103
|
+
readyEntitlement && !hasUsableSelfHostedLegacyFallback
|
|
104
|
+
? readyEntitlement
|
|
105
|
+
: null;
|
|
106
|
+
const activeEntitlement = featureAuthority?.active ? featureAuthority : null;
|
|
107
|
+
const resolvedStatus = activeEntitlement
|
|
108
|
+
? "valid"
|
|
109
|
+
: readyEntitlement?.source === "managedOrgSubscription"
|
|
110
|
+
? "none"
|
|
111
|
+
: featureAuthority
|
|
112
|
+
? (status ?? "none")
|
|
113
|
+
: (status ?? null);
|
|
74
114
|
const featuresEnabled =
|
|
75
115
|
resolvedStatus !== "expired" && resolvedStatus !== "invalid";
|
|
116
|
+
|
|
76
117
|
return {
|
|
77
118
|
status: resolvedStatus,
|
|
78
119
|
license: null,
|
|
79
|
-
checkFeature: () =>
|
|
80
|
-
|
|
120
|
+
checkFeature: (feature) =>
|
|
121
|
+
featureAuthority
|
|
122
|
+
? activeEntitlement
|
|
123
|
+
? feature === "threads" &&
|
|
124
|
+
Object.prototype.hasOwnProperty.call(
|
|
125
|
+
activeEntitlement.limits,
|
|
126
|
+
"threads.max_count",
|
|
127
|
+
)
|
|
128
|
+
? true
|
|
129
|
+
: (getOwnRecordValue(activeEntitlement.features, feature) ??
|
|
130
|
+
isLegacyUiFeature(feature))
|
|
131
|
+
: false
|
|
132
|
+
: featuresEnabled,
|
|
133
|
+
getLimit: (feature) =>
|
|
134
|
+
activeEntitlement
|
|
135
|
+
? (getOwnRecordValue(activeEntitlement.limits, feature) ?? null)
|
|
136
|
+
: null,
|
|
81
137
|
};
|
|
82
138
|
}
|
|
83
139
|
|