@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.
- 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 +162 -34
- 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/console-styling.cjs +3 -3
- package/dist/utils/console-styling.cjs.map +1 -1
- package/dist/utils/console-styling.mjs +3 -3
- package/dist/utils/console-styling.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/console-styling.ts +3 -3
- package/src/utils/types.ts +52 -0
|
@@ -2,7 +2,12 @@ import { Analytics } from "@segment/analytics-node";
|
|
|
2
2
|
import type { AnalyticsEvents } from "./events";
|
|
3
3
|
import { flattenObject } from "./utils";
|
|
4
4
|
import { v4 as uuidv4 } from "uuid";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
firstNonBlankTelemetryId,
|
|
7
|
+
lambdaClient,
|
|
8
|
+
parseAndWarnTelemetryId,
|
|
9
|
+
} from "./lambda-client";
|
|
10
|
+
import { computeSamplingMeta, TELEMETRY_EMITTER_V1 } from "./sampling";
|
|
6
11
|
|
|
7
12
|
/**
|
|
8
13
|
* Checks if telemetry is disabled via environment variables.
|
|
@@ -22,6 +27,26 @@ export function isTelemetryDisabled(): boolean {
|
|
|
22
27
|
);
|
|
23
28
|
}
|
|
24
29
|
|
|
30
|
+
/** Transport identity and sampling authority resolved for one runtime. */
|
|
31
|
+
export interface TelemetryIdentity {
|
|
32
|
+
telemetryId?: string;
|
|
33
|
+
licenseToken?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Capture-only telemetry client bound to one runtime identity. */
|
|
37
|
+
export interface TelemetryCapture {
|
|
38
|
+
capture<K extends keyof AnalyticsEvents>(
|
|
39
|
+
event: K,
|
|
40
|
+
properties: AnalyticsEvents[K],
|
|
41
|
+
): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface ResolvedTelemetryIdentity {
|
|
45
|
+
telemetryId: string | null;
|
|
46
|
+
licenseToken: string | null;
|
|
47
|
+
licenseTelemetryId: string | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
25
50
|
export class TelemetryClient {
|
|
26
51
|
segment: Analytics | undefined;
|
|
27
52
|
globalProperties: Record<string, any> = {};
|
|
@@ -30,11 +55,13 @@ export class TelemetryClient {
|
|
|
30
55
|
// client decodes its payload to extract telemetry_id. Customer API
|
|
31
56
|
// keys are NOT used here — they flow only into Segment.
|
|
32
57
|
private licenseToken: string | null = null;
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// absent or yielded no telemetry_id.
|
|
58
|
+
// Standalone analytics identity. This stays separate from the effective
|
|
59
|
+
// identity so legacy callers continue sending only their license token to
|
|
60
|
+
// the Lambda transport.
|
|
37
61
|
private telemetryId: string | null = null;
|
|
62
|
+
// License-derived identity used only as sampling authority. A standalone
|
|
63
|
+
// telemetry id remains a transport claim and does not bypass sampleRate.
|
|
64
|
+
private licenseTelemetryId: string | null = null;
|
|
38
65
|
packageName: string;
|
|
39
66
|
packageVersion: string;
|
|
40
67
|
private telemetryDisabled: boolean = false;
|
|
@@ -91,35 +118,55 @@ export class TelemetryClient {
|
|
|
91
118
|
async capture<K extends keyof AnalyticsEvents>(
|
|
92
119
|
event: K,
|
|
93
120
|
properties: AnalyticsEvents[K],
|
|
94
|
-
) {
|
|
121
|
+
): Promise<void> {
|
|
122
|
+
return this.captureWithIdentity(event, properties, {
|
|
123
|
+
telemetryId: this.telemetryId,
|
|
124
|
+
licenseToken: this.licenseToken,
|
|
125
|
+
licenseTelemetryId: this.licenseTelemetryId,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private async captureWithIdentity<K extends keyof AnalyticsEvents>(
|
|
130
|
+
event: K,
|
|
131
|
+
properties: AnalyticsEvents[K],
|
|
132
|
+
identity: ResolvedTelemetryIdentity,
|
|
133
|
+
): Promise<void> {
|
|
95
134
|
if (this.telemetryDisabled) {
|
|
96
135
|
return;
|
|
97
136
|
}
|
|
98
137
|
|
|
99
|
-
//
|
|
100
|
-
//
|
|
138
|
+
// Callers without license-derived sampling authority are gated by
|
|
139
|
+
// sampleRate. Legacy license tokens with telemetry_id always send —
|
|
101
140
|
// the volume is bounded by paying-customer count and full fidelity
|
|
102
141
|
// per identified customer is worth the marginal cost.
|
|
103
|
-
if (!
|
|
142
|
+
if (!identity.licenseTelemetryId && !this.shouldSendEvent()) {
|
|
104
143
|
return;
|
|
105
144
|
}
|
|
106
145
|
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
146
|
+
// Sampling metadata is computed in ./sampling so this client and the
|
|
147
|
+
// v2 runtime client can't drift apart again — see the note there.
|
|
148
|
+
const samplingMeta = computeSamplingMeta({
|
|
149
|
+
telemetryId: identity.licenseTelemetryId,
|
|
150
|
+
sampleRate: this.sampleRate,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Everything below travels identically on both copies of this event.
|
|
154
|
+
// The event id is what makes the dual-write dedupable downstream:
|
|
155
|
+
// one capture() produces one id, stamped on the lambda copy and the
|
|
156
|
+
// Segment copy alike, so consumers no longer have to infer the
|
|
157
|
+
// duplication from $lib or from which fields happen to be present
|
|
158
|
+
// (OSS-1019).
|
|
159
|
+
const eventMeta = {
|
|
160
|
+
...samplingMeta,
|
|
161
|
+
telemetry_emitter: TELEMETRY_EMITTER_V1,
|
|
162
|
+
telemetry_event_id: uuidv4(),
|
|
117
163
|
};
|
|
118
164
|
|
|
119
165
|
const flattenedProperties = flattenObject(properties);
|
|
120
166
|
const propertiesWithGlobal: Record<string, any> = {
|
|
121
167
|
...this.globalProperties,
|
|
122
|
-
...
|
|
168
|
+
...eventMeta,
|
|
169
|
+
telemetry_transport: "segment",
|
|
123
170
|
...flattenedProperties,
|
|
124
171
|
};
|
|
125
172
|
const orderedPropertiesWithGlobal = Object.keys(propertiesWithGlobal)
|
|
@@ -135,10 +182,15 @@ export class TelemetryClient {
|
|
|
135
182
|
await lambdaClient.send({
|
|
136
183
|
event,
|
|
137
184
|
properties: flattenedProperties,
|
|
138
|
-
globalProperties: {
|
|
185
|
+
globalProperties: {
|
|
186
|
+
...this.globalProperties,
|
|
187
|
+
...eventMeta,
|
|
188
|
+
telemetry_transport: "lambda",
|
|
189
|
+
},
|
|
139
190
|
packageName: this.packageName,
|
|
140
191
|
packageVersion: this.packageVersion,
|
|
141
|
-
|
|
192
|
+
telemetryId: identity.telemetryId ?? undefined,
|
|
193
|
+
licenseToken: identity.licenseToken ?? undefined,
|
|
142
194
|
});
|
|
143
195
|
|
|
144
196
|
if (this.segment) {
|
|
@@ -169,12 +221,75 @@ export class TelemetryClient {
|
|
|
169
221
|
});
|
|
170
222
|
}
|
|
171
223
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Atomically configure standalone, legacy, or anonymous telemetry identity.
|
|
226
|
+
*
|
|
227
|
+
* A standalone id takes transport precedence over a supplied legacy license
|
|
228
|
+
* token, but only a license-derived id grants sampling authority. Neither
|
|
229
|
+
* value is added to event properties.
|
|
230
|
+
*
|
|
231
|
+
* @param identity - One standalone id, one legacy license token, or neither.
|
|
232
|
+
*/
|
|
233
|
+
setTelemetryIdentity(identity: {
|
|
234
|
+
telemetryId?: string;
|
|
235
|
+
licenseToken?: string;
|
|
236
|
+
}): void {
|
|
237
|
+
const resolvedIdentity = this.resolveTelemetryIdentity(identity);
|
|
238
|
+
this.telemetryId = resolvedIdentity.telemetryId;
|
|
239
|
+
this.licenseToken = resolvedIdentity.licenseToken;
|
|
240
|
+
this.licenseTelemetryId = resolvedIdentity.licenseTelemetryId;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Configure legacy license-derived telemetry identity.
|
|
245
|
+
*
|
|
246
|
+
* @param licenseToken - License token whose telemetry claim identifies sends.
|
|
247
|
+
*/
|
|
175
248
|
setLicenseToken(licenseToken: string) {
|
|
176
|
-
this.licenseToken
|
|
177
|
-
|
|
249
|
+
this.setTelemetryIdentity({ licenseToken });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Create an immutable capture scope for one runtime.
|
|
254
|
+
*
|
|
255
|
+
* The scope shares this client's sinks, process-wide opt-out, global
|
|
256
|
+
* properties, and sampling settings, but snapshots transport identity and
|
|
257
|
+
* license-derived sampling authority. Constructing another runtime cannot
|
|
258
|
+
* rewrite an existing scope.
|
|
259
|
+
*
|
|
260
|
+
* @param identity - The runtime's construction-time telemetry identity.
|
|
261
|
+
* @returns A capture-only client bound to that identity.
|
|
262
|
+
*/
|
|
263
|
+
createScope(identity: TelemetryIdentity): TelemetryCapture {
|
|
264
|
+
const resolvedIdentity = this.resolveTelemetryIdentity(identity);
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
capture: <K extends keyof AnalyticsEvents>(
|
|
268
|
+
event: K,
|
|
269
|
+
properties: AnalyticsEvents[K],
|
|
270
|
+
) => this.captureWithIdentity(event, properties, resolvedIdentity),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private resolveTelemetryIdentity(
|
|
275
|
+
identity: TelemetryIdentity,
|
|
276
|
+
): ResolvedTelemetryIdentity {
|
|
277
|
+
const telemetryId = firstNonBlankTelemetryId(identity.telemetryId);
|
|
278
|
+
if (telemetryId !== undefined) {
|
|
279
|
+
return {
|
|
280
|
+
telemetryId,
|
|
281
|
+
licenseToken: null,
|
|
282
|
+
licenseTelemetryId: null,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
telemetryId: null,
|
|
288
|
+
licenseToken: identity.licenseToken ?? null,
|
|
289
|
+
licenseTelemetryId: identity.licenseToken
|
|
290
|
+
? parseAndWarnTelemetryId(identity.licenseToken)
|
|
291
|
+
: null,
|
|
292
|
+
};
|
|
178
293
|
}
|
|
179
294
|
|
|
180
295
|
private setSampleRate(sampleRate: number | undefined) {
|
|
@@ -198,8 +313,8 @@ export class TelemetryClient {
|
|
|
198
313
|
|
|
199
314
|
this.sampleRate = _sampleRate;
|
|
200
315
|
// Per-event sampling metadata (sampleRate/sampleRateAdjustmentFactor/
|
|
201
|
-
// sampleWeight) is computed
|
|
202
|
-
//
|
|
203
|
-
// population
|
|
316
|
+
// sampleWeight) is computed per capture() in ./sampling. Only license-
|
|
317
|
+
// authorized events get effectiveSampleRate=1; standalone transport
|
|
318
|
+
// identity stays in the sampled population.
|
|
204
319
|
}
|
|
205
320
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
executeConditions,
|
|
4
|
+
Condition,
|
|
5
|
+
ComparisonCondition,
|
|
6
|
+
LogicalCondition,
|
|
7
|
+
ExistenceCondition,
|
|
8
|
+
} from "../conditions";
|
|
9
|
+
|
|
10
|
+
describe("executeConditions", () => {
|
|
11
|
+
it("returns true when conditions is empty or undefined", () => {
|
|
12
|
+
expect(executeConditions({ value: { a: 1 } })).toBe(true);
|
|
13
|
+
expect(executeConditions({ conditions: [], value: { a: 1 } })).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("treats multiple conditions as an implicit AND", () => {
|
|
17
|
+
const conditions: Condition[] = [
|
|
18
|
+
{ rule: "EQUALS", path: "a", value: 1 },
|
|
19
|
+
{ rule: "EQUALS", path: "b", value: 2 },
|
|
20
|
+
];
|
|
21
|
+
expect(executeConditions({ conditions, value: { a: 1, b: 2 } })).toBe(true);
|
|
22
|
+
expect(executeConditions({ conditions, value: { a: 1, b: 3 } })).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("comparison rules", () => {
|
|
26
|
+
it("EQUALS and NOT_EQUALS", () => {
|
|
27
|
+
const eq: ComparisonCondition = { rule: "EQUALS", path: "a", value: 5 };
|
|
28
|
+
const neq: ComparisonCondition = { rule: "NOT_EQUALS", path: "a", value: 5 };
|
|
29
|
+
expect(executeConditions({ conditions: [eq], value: { a: 5 } })).toBe(true);
|
|
30
|
+
expect(executeConditions({ conditions: [eq], value: { a: 6 } })).toBe(false);
|
|
31
|
+
expect(executeConditions({ conditions: [neq], value: { a: 6 } })).toBe(true);
|
|
32
|
+
expect(executeConditions({ conditions: [neq], value: { a: 5 } })).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("GREATER_THAN and LESS_THAN", () => {
|
|
36
|
+
const gt: ComparisonCondition = { rule: "GREATER_THAN", path: "n", value: 10 };
|
|
37
|
+
const lt: ComparisonCondition = { rule: "LESS_THAN", path: "n", value: 10 };
|
|
38
|
+
expect(executeConditions({ conditions: [gt], value: { n: 11 } })).toBe(true);
|
|
39
|
+
expect(executeConditions({ conditions: [gt], value: { n: 10 } })).toBe(false);
|
|
40
|
+
expect(executeConditions({ conditions: [lt], value: { n: 9 } })).toBe(true);
|
|
41
|
+
expect(executeConditions({ conditions: [lt], value: { n: 10 } })).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("CONTAINS and NOT_CONTAINS only apply to arrays", () => {
|
|
45
|
+
const contains: ComparisonCondition = { rule: "CONTAINS", path: "tags", value: "ts" };
|
|
46
|
+
const notContains: ComparisonCondition = { rule: "NOT_CONTAINS", path: "tags", value: "ts" };
|
|
47
|
+
expect(
|
|
48
|
+
executeConditions({ conditions: [contains], value: { tags: ["js", "ts"] } }),
|
|
49
|
+
).toBe(true);
|
|
50
|
+
expect(
|
|
51
|
+
executeConditions({ conditions: [contains], value: { tags: ["js"] } }),
|
|
52
|
+
).toBe(false);
|
|
53
|
+
expect(
|
|
54
|
+
executeConditions({ conditions: [notContains], value: { tags: ["js"] } }),
|
|
55
|
+
).toBe(true);
|
|
56
|
+
// non-array target always fails both CONTAINS and NOT_CONTAINS
|
|
57
|
+
expect(
|
|
58
|
+
executeConditions({ conditions: [contains], value: { tags: "ts" } }),
|
|
59
|
+
).toBe(false);
|
|
60
|
+
expect(
|
|
61
|
+
executeConditions({ conditions: [notContains], value: { tags: "ts" } }),
|
|
62
|
+
).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("MATCHES uses the value as a regular expression", () => {
|
|
66
|
+
const matches: ComparisonCondition = { rule: "MATCHES", path: "code", value: "^CK-[0-9]+$" };
|
|
67
|
+
expect(executeConditions({ conditions: [matches], value: { code: "CK-123" } })).toBe(true);
|
|
68
|
+
expect(executeConditions({ conditions: [matches], value: { code: "nope" } })).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("STARTS_WITH and ENDS_WITH", () => {
|
|
72
|
+
const sw: ComparisonCondition = { rule: "STARTS_WITH", path: "s", value: "foo" };
|
|
73
|
+
const ew: ComparisonCondition = { rule: "ENDS_WITH", path: "s", value: "bar" };
|
|
74
|
+
expect(executeConditions({ conditions: [sw], value: { s: "foobar" } })).toBe(true);
|
|
75
|
+
expect(executeConditions({ conditions: [sw], value: { s: "barfoo" } })).toBe(false);
|
|
76
|
+
expect(executeConditions({ conditions: [ew], value: { s: "foobar" } })).toBe(true);
|
|
77
|
+
expect(executeConditions({ conditions: [ew], value: { s: "barfoo" } })).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("existence rules", () => {
|
|
82
|
+
it("EXISTS passes only for defined non-null values", () => {
|
|
83
|
+
const exists: ExistenceCondition = { rule: "EXISTS", path: "a" };
|
|
84
|
+
expect(executeConditions({ conditions: [exists], value: { a: 0 } })).toBe(true);
|
|
85
|
+
expect(executeConditions({ conditions: [exists], value: { a: "" } })).toBe(true);
|
|
86
|
+
expect(executeConditions({ conditions: [exists], value: { a: false } })).toBe(true);
|
|
87
|
+
expect(executeConditions({ conditions: [exists], value: { a: null } })).toBe(false);
|
|
88
|
+
expect(executeConditions({ conditions: [exists], value: {} })).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("NOT_EXISTS is the inverse of EXISTS", () => {
|
|
92
|
+
const notExists: ExistenceCondition = { rule: "NOT_EXISTS", path: "a" };
|
|
93
|
+
expect(executeConditions({ conditions: [notExists], value: { a: 1 } })).toBe(false);
|
|
94
|
+
expect(executeConditions({ conditions: [notExists], value: { a: null } })).toBe(true);
|
|
95
|
+
expect(executeConditions({ conditions: [notExists], value: {} })).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("logical rules", () => {
|
|
100
|
+
it("AND requires all nested conditions to pass", () => {
|
|
101
|
+
const and: LogicalCondition = {
|
|
102
|
+
rule: "AND",
|
|
103
|
+
conditions: [
|
|
104
|
+
{ rule: "EQUALS", path: "a", value: 1 },
|
|
105
|
+
{ rule: "EQUALS", path: "b", value: 2 },
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
expect(executeConditions({ conditions: [and], value: { a: 1, b: 2 } })).toBe(true);
|
|
109
|
+
expect(executeConditions({ conditions: [and], value: { a: 1, b: 3 } })).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("OR passes when any nested condition passes", () => {
|
|
113
|
+
const or: LogicalCondition = {
|
|
114
|
+
rule: "OR",
|
|
115
|
+
conditions: [
|
|
116
|
+
{ rule: "EQUALS", path: "a", value: 1 },
|
|
117
|
+
{ rule: "EQUALS", path: "b", value: 2 },
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
expect(executeConditions({ conditions: [or], value: { a: 9, b: 2 } })).toBe(true);
|
|
121
|
+
expect(executeConditions({ conditions: [or], value: { a: 9, b: 9 } })).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("NOT inverts the AND of nested conditions", () => {
|
|
125
|
+
const notAll: LogicalCondition = {
|
|
126
|
+
rule: "NOT",
|
|
127
|
+
conditions: [
|
|
128
|
+
{ rule: "EQUALS", path: "a", value: 1 },
|
|
129
|
+
{ rule: "EQUALS", path: "b", value: 2 },
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
// both true → NOT(AND) = false
|
|
133
|
+
expect(executeConditions({ conditions: [notAll], value: { a: 1, b: 2 } })).toBe(false);
|
|
134
|
+
// one false → NOT(AND) = true
|
|
135
|
+
expect(executeConditions({ conditions: [notAll], value: { a: 1, b: 3 } })).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("path resolution", () => {
|
|
140
|
+
it("resolves nested dot paths", () => {
|
|
141
|
+
const cond: ComparisonCondition = { rule: "EQUALS", path: "user.profile.age", value: 30 };
|
|
142
|
+
expect(
|
|
143
|
+
executeConditions({ conditions: [cond], value: { user: { profile: { age: 30 } } } }),
|
|
144
|
+
).toBe(true);
|
|
145
|
+
expect(
|
|
146
|
+
executeConditions({ conditions: [cond], value: { user: { profile: { age: 31 } } } }),
|
|
147
|
+
).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("returns false for a missing nested path (undefined target)", () => {
|
|
151
|
+
const eq: ComparisonCondition = { rule: "EQUALS", path: "a.b.c", value: 1 };
|
|
152
|
+
expect(executeConditions({ conditions: [eq], value: {} })).toBe(false);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("uses the whole value when no path is given", () => {
|
|
156
|
+
const eq: ComparisonCondition = { rule: "EQUALS", value: 42 };
|
|
157
|
+
expect(executeConditions({ conditions: [eq], value: 42 })).toBe(true);
|
|
158
|
+
expect(executeConditions({ conditions: [eq], value: 43 })).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -53,15 +53,15 @@ export function logCopilotKitPlatformMessage() {
|
|
|
53
53
|
console.log(
|
|
54
54
|
`%cCopilotKit Warning%c
|
|
55
55
|
|
|
56
|
-
useCopilotChatHeadless_c provides full compatibility with CopilotKit's newly released Headless UI feature set.
|
|
56
|
+
useCopilotChatHeadless_c provides full compatibility with CopilotKit's newly released Headless UI feature set. Headless UI requires a CopilotKit Intelligence license key, available for free at:
|
|
57
57
|
|
|
58
58
|
%chttps://dashboard.operations.copilotkit.ai%c
|
|
59
59
|
|
|
60
60
|
Alternatively, useCopilotChat is available for basic programmatic control, and does not require a license key.
|
|
61
61
|
|
|
62
|
-
To learn more about
|
|
62
|
+
To learn more about CopilotKit Intelligence, read the documentation here:
|
|
63
63
|
|
|
64
|
-
%chttps://docs.copilotkit.ai/
|
|
64
|
+
%chttps://docs.copilotkit.ai/intelligence/overview%c`,
|
|
65
65
|
ConsoleStyles.header,
|
|
66
66
|
ConsoleStyles.body,
|
|
67
67
|
ConsoleStyles.cta,
|
package/src/utils/types.ts
CHANGED
|
@@ -46,6 +46,55 @@ export type RuntimeLicenseStatus =
|
|
|
46
46
|
| "invalid"
|
|
47
47
|
| "unknown";
|
|
48
48
|
|
|
49
|
+
/** Runtime entitlement authority resolved by a managed or self-hosted backend. */
|
|
50
|
+
interface RuntimeEntitlement {
|
|
51
|
+
/** Whether the resolved entitlement currently grants product access. */
|
|
52
|
+
active: boolean;
|
|
53
|
+
/** Deployment authority that produced this entitlement. */
|
|
54
|
+
source: "managedOrgSubscription" | "selfHostedDeploymentLicense";
|
|
55
|
+
/** Boolean feature grants keyed by stable feature id. */
|
|
56
|
+
features: Record<string, boolean>;
|
|
57
|
+
/** Numeric limits keyed by stable feature id. */
|
|
58
|
+
limits: Record<string, number>;
|
|
59
|
+
/** Optional catalog plan code supplied by the entitlement authority. */
|
|
60
|
+
planCode?: string;
|
|
61
|
+
/** Optional lower-level source metadata supplied by the authority. */
|
|
62
|
+
entitlementSource?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Public diagnostic returned when Runtime entitlement resolution is not ready. */
|
|
66
|
+
interface RuntimeEntitlementError {
|
|
67
|
+
/** Stable backend or SDK error code. */
|
|
68
|
+
code: string;
|
|
69
|
+
/** Safe human-readable diagnostic. */
|
|
70
|
+
message: string;
|
|
71
|
+
/** Whether a later resolution attempt may succeed without reconfiguration. */
|
|
72
|
+
retryable: boolean;
|
|
73
|
+
/** Optional originating request correlation id. */
|
|
74
|
+
requestId?: string;
|
|
75
|
+
/** Optional originating trace correlation id. */
|
|
76
|
+
traceId?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Successfully resolved Runtime entitlement response. */
|
|
80
|
+
interface RuntimeEntitlementReadyResponse {
|
|
81
|
+
status: "ready";
|
|
82
|
+
entitlement: RuntimeEntitlement;
|
|
83
|
+
error?: never;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Structured non-ready Runtime entitlement response. */
|
|
87
|
+
interface RuntimeEntitlementErrorResponse {
|
|
88
|
+
status: "degraded" | "misconfigured" | "unavailable";
|
|
89
|
+
entitlement?: never;
|
|
90
|
+
error: RuntimeEntitlementError;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Final structured Runtime entitlement response exposed through `/info`. */
|
|
94
|
+
export type RuntimeEntitlementResponse =
|
|
95
|
+
| RuntimeEntitlementReadyResponse
|
|
96
|
+
| RuntimeEntitlementErrorResponse;
|
|
97
|
+
|
|
49
98
|
export interface A2UIRuntimeInfo {
|
|
50
99
|
enabled: boolean;
|
|
51
100
|
/**
|
|
@@ -77,6 +126,9 @@ export interface RuntimeInfo {
|
|
|
77
126
|
a2uiEnabled?: boolean;
|
|
78
127
|
a2ui?: A2UIRuntimeInfo;
|
|
79
128
|
openGenerativeUIEnabled?: boolean;
|
|
129
|
+
/** Structured Runtime-level entitlement authority, when advertised. */
|
|
130
|
+
runtimeEntitlements?: RuntimeEntitlementResponse;
|
|
131
|
+
/** Legacy compatibility diagnostic retained for older Core/Inspector clients. */
|
|
80
132
|
licenseStatus?: RuntimeLicenseStatus;
|
|
81
133
|
telemetryDisabled?: boolean;
|
|
82
134
|
}
|