@copilotkit/shared 1.69.3 → 1.70.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.
Files changed (63) hide show
  1. package/dist/index.cjs +31 -13
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +19 -17
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.mts +19 -17
  6. package/dist/index.d.mts.map +1 -1
  7. package/dist/index.mjs +29 -15
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/index.umd.js +162 -34
  10. package/dist/index.umd.js.map +1 -1
  11. package/dist/package.cjs +1 -1
  12. package/dist/package.mjs +1 -1
  13. package/dist/telemetry/index.d.mts +3 -2
  14. package/dist/telemetry/lambda-client.cjs +25 -4
  15. package/dist/telemetry/lambda-client.cjs.map +1 -1
  16. package/dist/telemetry/lambda-client.d.cts +14 -1
  17. package/dist/telemetry/lambda-client.d.cts.map +1 -1
  18. package/dist/telemetry/lambda-client.d.mts +14 -1
  19. package/dist/telemetry/lambda-client.d.mts.map +1 -1
  20. package/dist/telemetry/lambda-client.mjs +25 -5
  21. package/dist/telemetry/lambda-client.mjs.map +1 -1
  22. package/dist/telemetry/sampling.cjs +28 -0
  23. package/dist/telemetry/sampling.cjs.map +1 -0
  24. package/dist/telemetry/sampling.d.cts +37 -0
  25. package/dist/telemetry/sampling.d.cts.map +1 -0
  26. package/dist/telemetry/sampling.d.mts +37 -0
  27. package/dist/telemetry/sampling.d.mts.map +1 -0
  28. package/dist/telemetry/sampling.mjs +25 -0
  29. package/dist/telemetry/sampling.mjs.map +1 -0
  30. package/dist/telemetry/telemetry-client.cjs +72 -11
  31. package/dist/telemetry/telemetry-client.cjs.map +1 -1
  32. package/dist/telemetry/telemetry-client.d.cts +43 -1
  33. package/dist/telemetry/telemetry-client.d.cts.map +1 -1
  34. package/dist/telemetry/telemetry-client.d.mts +43 -1
  35. package/dist/telemetry/telemetry-client.d.mts.map +1 -1
  36. package/dist/telemetry/telemetry-client.mjs +73 -12
  37. package/dist/telemetry/telemetry-client.mjs.map +1 -1
  38. package/dist/utils/console-styling.cjs +3 -3
  39. package/dist/utils/console-styling.cjs.map +1 -1
  40. package/dist/utils/console-styling.mjs +3 -3
  41. package/dist/utils/console-styling.mjs.map +1 -1
  42. package/dist/utils/index.d.cts +1 -1
  43. package/dist/utils/index.d.mts +1 -1
  44. package/dist/utils/types.cjs.map +1 -1
  45. package/dist/utils/types.d.cts +46 -1
  46. package/dist/utils/types.d.cts.map +1 -1
  47. package/dist/utils/types.d.mts +46 -1
  48. package/dist/utils/types.d.mts.map +1 -1
  49. package/dist/utils/types.mjs.map +1 -1
  50. package/package.json +2 -2
  51. package/src/__tests__/license-context.test.ts +224 -25
  52. package/src/index.ts +72 -16
  53. package/src/telemetry/index.ts +2 -0
  54. package/src/telemetry/lambda-client.test.ts +336 -1
  55. package/src/telemetry/lambda-client.ts +56 -15
  56. package/src/telemetry/sampling.test.ts +65 -0
  57. package/src/telemetry/sampling.ts +70 -0
  58. package/src/telemetry/telemetry-blank-license-identity.test.ts +121 -0
  59. package/src/telemetry/telemetry-client.test.ts +438 -15
  60. package/src/telemetry/telemetry-client.ts +145 -30
  61. package/src/utils/__tests__/conditions.test.ts +161 -0
  62. package/src/utils/console-styling.ts +3 -3
  63. package/src/utils/types.ts +52 -0
@@ -28,6 +28,48 @@ interface ThreadEndpointRuntimeInfo {
28
28
  realtimeMetadata: boolean;
29
29
  }
30
30
  type RuntimeLicenseStatus = "valid" | "none" | "expired" | "expiring" | "invalid" | "unknown";
31
+ /** Runtime entitlement authority resolved by a managed or self-hosted backend. */
32
+ interface RuntimeEntitlement {
33
+ /** Whether the resolved entitlement currently grants product access. */
34
+ active: boolean;
35
+ /** Deployment authority that produced this entitlement. */
36
+ source: "managedOrgSubscription" | "selfHostedDeploymentLicense";
37
+ /** Boolean feature grants keyed by stable feature id. */
38
+ features: Record<string, boolean>;
39
+ /** Numeric limits keyed by stable feature id. */
40
+ limits: Record<string, number>;
41
+ /** Optional catalog plan code supplied by the entitlement authority. */
42
+ planCode?: string;
43
+ /** Optional lower-level source metadata supplied by the authority. */
44
+ entitlementSource?: string;
45
+ }
46
+ /** Public diagnostic returned when Runtime entitlement resolution is not ready. */
47
+ interface RuntimeEntitlementError {
48
+ /** Stable backend or SDK error code. */
49
+ code: string;
50
+ /** Safe human-readable diagnostic. */
51
+ message: string;
52
+ /** Whether a later resolution attempt may succeed without reconfiguration. */
53
+ retryable: boolean;
54
+ /** Optional originating request correlation id. */
55
+ requestId?: string;
56
+ /** Optional originating trace correlation id. */
57
+ traceId?: string;
58
+ }
59
+ /** Successfully resolved Runtime entitlement response. */
60
+ interface RuntimeEntitlementReadyResponse {
61
+ status: "ready";
62
+ entitlement: RuntimeEntitlement;
63
+ error?: never;
64
+ }
65
+ /** Structured non-ready Runtime entitlement response. */
66
+ interface RuntimeEntitlementErrorResponse {
67
+ status: "degraded" | "misconfigured" | "unavailable";
68
+ entitlement?: never;
69
+ error: RuntimeEntitlementError;
70
+ }
71
+ /** Final structured Runtime entitlement response exposed through `/info`. */
72
+ type RuntimeEntitlementResponse = RuntimeEntitlementReadyResponse | RuntimeEntitlementErrorResponse;
31
73
  interface A2UIRuntimeInfo {
32
74
  enabled: boolean;
33
75
  /**
@@ -58,9 +100,12 @@ interface RuntimeInfo {
58
100
  a2uiEnabled?: boolean;
59
101
  a2ui?: A2UIRuntimeInfo;
60
102
  openGenerativeUIEnabled?: boolean;
103
+ /** Structured Runtime-level entitlement authority, when advertised. */
104
+ runtimeEntitlements?: RuntimeEntitlementResponse;
105
+ /** Legacy compatibility diagnostic retained for older Core/Inspector clients. */
61
106
  licenseStatus?: RuntimeLicenseStatus;
62
107
  telemetryDisabled?: boolean;
63
108
  }
64
109
  //#endregion
65
- export { A2UIRuntimeInfo, AgentDescription, IntelligenceRuntimeInfo, MaybePromise, NonEmptyRecord, RUNTIME_MODE_INTELLIGENCE, RUNTIME_MODE_SSE, RuntimeInfo, RuntimeLicenseStatus, RuntimeMode, ThreadEndpointRuntimeInfo };
110
+ export { A2UIRuntimeInfo, AgentDescription, IntelligenceRuntimeInfo, MaybePromise, NonEmptyRecord, RUNTIME_MODE_INTELLIGENCE, RUNTIME_MODE_SSE, RuntimeEntitlementResponse, RuntimeInfo, RuntimeLicenseStatus, RuntimeMode, ThreadEndpointRuntimeInfo };
66
111
  //# sourceMappingURL=types.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../src/utils/types.ts"],"mappings":";;;KAEY,YAAA,MAAkB,CAAA,GAAI,WAAA,CAAY,CAAA;;AAA9C;;KAKY,cAAA,MACV,CAAA,SAAU,MAAA,0BACA,CAAA,yBAEJ,CAAA;;;;UAMS,gBAAA;EACf,IAAA;EACA,SAAA;EACA,WAAA;EACA,YAAA,GAAe,iBAAA;AAAA;AAAA,KAGL,WAAA;AAAA,cAEC,gBAAA;AAAA,cACA,yBAAA;AAAA,UAEI,uBAAA;EACf,KAAA;AAAA;AAAA,UAGe,yBAAA;EACf,IAAA;EACA,OAAA;EACA,SAAA;EACA,gBAAA;AAAA;AAAA,KAGU,oBAAA;AAAA,UAQK,eAAA;EACf,OAAA;EAtCM;;;AAMR;EAqCE,MAAA;AAAA;AAAA,UAGe,WAAA;EACf,OAAA;EACA,MAAA,EAAQ,MAAA,SAAe,gBAAA;EACvB,6BAAA;EACA,IAAA,EAAM,WAAA;EACN,YAAA,GAAe,uBAAA;EACf,eAAA,GAAkB,yBAAA;EA1Cc;EA4ChC,iBAAA;EAzCqB;;;;AAEvB;EA6CE,WAAA;;;;AA5CF;EAiDE,WAAA;EACA,IAAA,GAAO,eAAA;EACP,uBAAA;EACA,aAAA,GAAgB,oBAAA;EAChB,iBAAA;AAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../src/utils/types.ts"],"mappings":";;;KAEY,YAAA,MAAkB,CAAA,GAAI,WAAA,CAAY,CAAA;;AAA9C;;KAKY,cAAA,MACV,CAAA,SAAU,MAAA,0BACA,CAAA,yBAEJ,CAAA;;;;UAMS,gBAAA;EACf,IAAA;EACA,SAAA;EACA,WAAA;EACA,YAAA,GAAe,iBAAA;AAAA;AAAA,KAGL,WAAA;AAAA,cAEC,gBAAA;AAAA,cACA,yBAAA;AAAA,UAEI,uBAAA;EACf,KAAA;AAAA;AAAA,UAGe,yBAAA;EACf,IAAA;EACA,OAAA;EACA,SAAA;EACA,gBAAA;AAAA;AAAA,KAGU,oBAAA;;UASF,kBAAA;EAtCF;EAwCN,MAAA;EAxCO;EA0CP,MAAA;EApC+B;EAsC/B,QAAA,EAAU,MAAA;EAlCsB;EAoChC,MAAA,EAAQ,MAAA;EAtCR;EAwCA,QAAA;EAtCA;EAwCA,iBAAA;AAAA;;UAIQ,uBAAA;EAzCa;EA2CrB,IAAA;EA3CqB;EA6CrB,OAAA;EA3CW;EA6CX,SAAA;;EAEA,SAAA;EA/C4C;EAiD5C,OAAA;AAAA;;UAIQ,+BAAA;EACR,MAAA;EACA,WAAA,EAAa,kBAAA;EACb,KAAA;AAAA;;UAIQ,+BAAA;EACR,MAAA;EACA,WAAA;EACA,KAAA,EAAO,uBAAA;AAAA;;KAIG,0BAAA,GACR,+BAAA,GACA,+BAAA;AAAA,UAEa,eAAA;EACf,OAAA;EA7DgB;;AAGlB;;EA+DE,MAAA;AAAA;AAAA,UAGe,WAAA;EACf,OAAA;EACA,MAAA,EAAQ,MAAA,SAAe,gBAAA;EACvB,6BAAA;EACA,IAAA,EAAM,WAAA;EACN,YAAA,GAAe,uBAAA;EACf,eAAA,GAAkB,yBAAA;EAzDlB;EA2DA,iBAAA;EAzDA;;;;;EA+DA,WAAA;EAvDQ;;;;EA4DR,WAAA;EACA,IAAA,GAAO,eAAA;EACP,uBAAA;EAtDA;EAwDA,mBAAA,GAAsB,0BAAA;EAtDf;EAwDP,aAAA,GAAgB,oBAAA;EAChB,iBAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.mjs","names":[],"sources":["../../src/utils/types.ts"],"sourcesContent":["import type { AgentCapabilities } from \"@ag-ui/core\";\n\nexport type MaybePromise<T> = T | PromiseLike<T>;\n\n/**\n * More specific utility for records with at least one key\n */\nexport type NonEmptyRecord<T> =\n T extends Record<string, unknown>\n ? keyof T extends never\n ? never\n : T\n : never;\n\n/**\n * Type representing an agent's basic information\n */\nexport interface AgentDescription {\n name: string;\n className: string;\n description: string;\n capabilities?: AgentCapabilities;\n}\n\nexport type RuntimeMode = \"sse\" | \"intelligence\";\n\nexport const RUNTIME_MODE_SSE = \"sse\" as const;\nexport const RUNTIME_MODE_INTELLIGENCE = \"intelligence\" as const;\n\nexport interface IntelligenceRuntimeInfo {\n wsUrl: string;\n}\n\nexport interface ThreadEndpointRuntimeInfo {\n list: boolean;\n inspect: boolean;\n mutations: boolean;\n realtimeMetadata: boolean;\n}\n\nexport type RuntimeLicenseStatus =\n | \"valid\"\n | \"none\"\n | \"expired\"\n | \"expiring\"\n | \"invalid\"\n | \"unknown\";\n\nexport interface A2UIRuntimeInfo {\n enabled: boolean;\n /**\n * Agent ids the runtime applies A2UI to. When omitted, A2UI applies to\n * every agent served by the runtime.\n */\n agents?: string[];\n}\n\nexport interface RuntimeInfo {\n version: string;\n agents: Record<string, AgentDescription>;\n audioFileTranscriptionEnabled: boolean;\n mode: RuntimeMode;\n intelligence?: IntelligenceRuntimeInfo;\n threadEndpoints?: ThreadEndpointRuntimeInfo;\n /** Whether this runtime exposes trusted inspector metadata. */\n inspectorMetadata?: boolean;\n /**\n * When true, the runtime exposes POST /agent/:agentId/suggest for stateless\n * suggestion generation. Absent on older runtimes; clients fall back to a\n * client-side agent run.\n */\n suggestions?: boolean;\n /**\n * @deprecated Use `a2ui` instead, which preserves per-agent scoping.\n * Kept for backward compatibility with older clients.\n */\n a2uiEnabled?: boolean;\n a2ui?: A2UIRuntimeInfo;\n openGenerativeUIEnabled?: boolean;\n licenseStatus?: RuntimeLicenseStatus;\n telemetryDisabled?: boolean;\n}\n"],"mappings":";AA0BA,MAAa,mBAAmB;AAChC,MAAa,4BAA4B"}
1
+ {"version":3,"file":"types.mjs","names":[],"sources":["../../src/utils/types.ts"],"sourcesContent":["import type { AgentCapabilities } from \"@ag-ui/core\";\n\nexport type MaybePromise<T> = T | PromiseLike<T>;\n\n/**\n * More specific utility for records with at least one key\n */\nexport type NonEmptyRecord<T> =\n T extends Record<string, unknown>\n ? keyof T extends never\n ? never\n : T\n : never;\n\n/**\n * Type representing an agent's basic information\n */\nexport interface AgentDescription {\n name: string;\n className: string;\n description: string;\n capabilities?: AgentCapabilities;\n}\n\nexport type RuntimeMode = \"sse\" | \"intelligence\";\n\nexport const RUNTIME_MODE_SSE = \"sse\" as const;\nexport const RUNTIME_MODE_INTELLIGENCE = \"intelligence\" as const;\n\nexport interface IntelligenceRuntimeInfo {\n wsUrl: string;\n}\n\nexport interface ThreadEndpointRuntimeInfo {\n list: boolean;\n inspect: boolean;\n mutations: boolean;\n realtimeMetadata: boolean;\n}\n\nexport type RuntimeLicenseStatus =\n | \"valid\"\n | \"none\"\n | \"expired\"\n | \"expiring\"\n | \"invalid\"\n | \"unknown\";\n\n/** Runtime entitlement authority resolved by a managed or self-hosted backend. */\ninterface RuntimeEntitlement {\n /** Whether the resolved entitlement currently grants product access. */\n active: boolean;\n /** Deployment authority that produced this entitlement. */\n source: \"managedOrgSubscription\" | \"selfHostedDeploymentLicense\";\n /** Boolean feature grants keyed by stable feature id. */\n features: Record<string, boolean>;\n /** Numeric limits keyed by stable feature id. */\n limits: Record<string, number>;\n /** Optional catalog plan code supplied by the entitlement authority. */\n planCode?: string;\n /** Optional lower-level source metadata supplied by the authority. */\n entitlementSource?: string;\n}\n\n/** Public diagnostic returned when Runtime entitlement resolution is not ready. */\ninterface RuntimeEntitlementError {\n /** Stable backend or SDK error code. */\n code: string;\n /** Safe human-readable diagnostic. */\n message: string;\n /** Whether a later resolution attempt may succeed without reconfiguration. */\n retryable: boolean;\n /** Optional originating request correlation id. */\n requestId?: string;\n /** Optional originating trace correlation id. */\n traceId?: string;\n}\n\n/** Successfully resolved Runtime entitlement response. */\ninterface RuntimeEntitlementReadyResponse {\n status: \"ready\";\n entitlement: RuntimeEntitlement;\n error?: never;\n}\n\n/** Structured non-ready Runtime entitlement response. */\ninterface RuntimeEntitlementErrorResponse {\n status: \"degraded\" | \"misconfigured\" | \"unavailable\";\n entitlement?: never;\n error: RuntimeEntitlementError;\n}\n\n/** Final structured Runtime entitlement response exposed through `/info`. */\nexport type RuntimeEntitlementResponse =\n | RuntimeEntitlementReadyResponse\n | RuntimeEntitlementErrorResponse;\n\nexport interface A2UIRuntimeInfo {\n enabled: boolean;\n /**\n * Agent ids the runtime applies A2UI to. When omitted, A2UI applies to\n * every agent served by the runtime.\n */\n agents?: string[];\n}\n\nexport interface RuntimeInfo {\n version: string;\n agents: Record<string, AgentDescription>;\n audioFileTranscriptionEnabled: boolean;\n mode: RuntimeMode;\n intelligence?: IntelligenceRuntimeInfo;\n threadEndpoints?: ThreadEndpointRuntimeInfo;\n /** Whether this runtime exposes trusted inspector metadata. */\n inspectorMetadata?: boolean;\n /**\n * When true, the runtime exposes POST /agent/:agentId/suggest for stateless\n * suggestion generation. Absent on older runtimes; clients fall back to a\n * client-side agent run.\n */\n suggestions?: boolean;\n /**\n * @deprecated Use `a2ui` instead, which preserves per-agent scoping.\n * Kept for backward compatibility with older clients.\n */\n a2uiEnabled?: boolean;\n a2ui?: A2UIRuntimeInfo;\n openGenerativeUIEnabled?: boolean;\n /** Structured Runtime-level entitlement authority, when advertised. */\n runtimeEntitlements?: RuntimeEntitlementResponse;\n /** Legacy compatibility diagnostic retained for older Core/Inspector clients. */\n licenseStatus?: RuntimeLicenseStatus;\n telemetryDisabled?: boolean;\n}\n"],"mappings":";AA0BA,MAAa,mBAAmB;AAChC,MAAa,4BAA4B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@copilotkit/shared",
3
- "version": "1.69.3",
3
+ "version": "1.70.1",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "ai",
@@ -38,7 +38,7 @@
38
38
  "access": "public"
39
39
  },
40
40
  "dependencies": {
41
- "@ag-ui/client": "0.0.57",
41
+ "@ag-ui/client": "0.0.59",
42
42
  "@copilotkit/license-verifier": "~0.5.0",
43
43
  "@segment/analytics-node": "^2.1.2",
44
44
  "@standard-schema/spec": "^1.0.0",
@@ -1,33 +1,232 @@
1
- import { describe, it, expect } from "vitest";
1
+ import { expect, test } from "vitest";
2
2
  import { createLicenseContextValue } from "../index";
3
3
  import type { RuntimeLicenseStatus } from "../utils/types";
4
4
 
5
- describe("createLicenseContextValue", () => {
6
- it("fails open when no status is known", () => {
7
- for (const status of [null, undefined] as const) {
8
- const ctx = createLicenseContextValue(status);
9
- expect(ctx.status).toBeNull();
10
- expect(ctx.license).toBeNull();
11
- expect(ctx.checkFeature("chat")).toBe(true);
12
- expect(ctx.getLimit("chat")).toBeNull();
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
- it.each(["valid", "none", "expiring", "unknown"] as RuntimeLicenseStatus[])(
17
- "enables features for %s status",
18
- (status) => {
19
- const ctx = createLicenseContextValue(status);
20
- expect(ctx.status).toBe(status);
21
- expect(ctx.checkFeature("chat")).toBe(true);
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
- it.each(["expired", "invalid"] as RuntimeLicenseStatus[])(
26
- "disables features for %s status",
27
- (status) => {
28
- const ctx = createLicenseContextValue(status);
29
- expect(ctx.status).toBe(status);
30
- expect(ctx.checkFeature("chat")).toBe(false);
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 { RuntimeLicenseStatus } from "./utils/types";
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
- /** Server-reported license status from the runtime's /info endpoint. Null until known. */
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 specific feature is licensed. Returns true if no licensing is active (no token). */
54
+ /** Whether a feature is licensed. Ready entitlements override legacy status behavior. */
52
55
  checkFeature: (feature: string) => boolean;
53
- /** Get a numeric feature limit. Returns null if not applicable. */
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 status the
76
+ * Client-safe license context factory, driven by the license authority the
59
77
  * runtime reports via /info.
60
78
  *
61
- * Features are enabled unless the runtime definitively reports the license
62
- * as "expired" or "invalid". A null/"none"/"unknown" status fails open
63
- * (unlicensed = unrestricted, with branding), and "expiring" keeps features
64
- * on while the provider surfaces a warning banner. Per-feature data is not
65
- * in /info yet, so checkFeature is uniform across features and getLimit has
66
- * no limits to report. This is inlined here to avoid importing the full
67
- * license-verifier bundle (which depends on Node's `crypto`) into browser
68
- * bundles.
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 resolvedStatus = status ?? null;
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: () => featuresEnabled,
80
- getLimit: () => null,
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
 
@@ -1,5 +1,7 @@
1
1
  export * from "./telemetry-client";
2
+ export * from "./sampling";
2
3
  export {
4
+ firstNonBlankTelemetryId,
3
5
  lambdaClient,
4
6
  parseTelemetryIdFromLicense,
5
7
  parseAndWarnTelemetryId,