@copilotkit/shared 1.70.0 → 1.70.2

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 (75) hide show
  1. package/dist/attachments/types.d.cts +10 -0
  2. package/dist/attachments/types.d.cts.map +1 -1
  3. package/dist/attachments/types.d.mts +10 -0
  4. package/dist/attachments/types.d.mts.map +1 -1
  5. package/dist/index.cjs +8 -5
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +5 -3
  8. package/dist/index.d.cts.map +1 -1
  9. package/dist/index.d.mts +5 -4
  10. package/dist/index.d.mts.map +1 -1
  11. package/dist/index.mjs +5 -4
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/index.umd.js +229 -200
  14. package/dist/index.umd.js.map +1 -1
  15. package/dist/package.cjs +1 -1
  16. package/dist/package.mjs +1 -1
  17. package/dist/telemetry/index.cjs +15 -0
  18. package/dist/telemetry/index.d.cts +5 -0
  19. package/dist/telemetry/index.d.mts +4 -2
  20. package/dist/telemetry/index.mjs +6 -0
  21. package/dist/telemetry/telemetry-client.cjs +4 -13
  22. package/dist/telemetry/telemetry-client.cjs.map +1 -1
  23. package/dist/telemetry/telemetry-client.d.cts +2 -8
  24. package/dist/telemetry/telemetry-client.d.cts.map +1 -1
  25. package/dist/telemetry/telemetry-client.d.mts +2 -8
  26. package/dist/telemetry/telemetry-client.d.mts.map +1 -1
  27. package/dist/telemetry/telemetry-client.mjs +4 -12
  28. package/dist/telemetry/telemetry-client.mjs.map +1 -1
  29. package/dist/telemetry/telemetry-disabled.cjs +20 -0
  30. package/dist/telemetry/telemetry-disabled.cjs.map +1 -0
  31. package/dist/telemetry/telemetry-disabled.d.cts +16 -0
  32. package/dist/telemetry/telemetry-disabled.d.cts.map +1 -0
  33. package/dist/telemetry/telemetry-disabled.d.mts +16 -0
  34. package/dist/telemetry/telemetry-disabled.d.mts.map +1 -0
  35. package/dist/telemetry/telemetry-disabled.mjs +19 -0
  36. package/dist/telemetry/telemetry-disabled.mjs.map +1 -0
  37. package/dist/utils/console-styling.cjs +3 -3
  38. package/dist/utils/console-styling.cjs.map +1 -1
  39. package/dist/utils/console-styling.mjs +3 -3
  40. package/dist/utils/console-styling.mjs.map +1 -1
  41. package/dist/utils/index.cjs +1 -0
  42. package/dist/utils/index.cjs.map +1 -1
  43. package/dist/utils/index.d.cts +2 -1
  44. package/dist/utils/index.d.cts.map +1 -1
  45. package/dist/utils/index.d.mts +2 -1
  46. package/dist/utils/index.d.mts.map +1 -1
  47. package/dist/utils/index.mjs +1 -0
  48. package/dist/utils/index.mjs.map +1 -1
  49. package/dist/utils/inspector-learning.cjs +185 -0
  50. package/dist/utils/inspector-learning.cjs.map +1 -0
  51. package/dist/utils/inspector-learning.d.cts +91 -0
  52. package/dist/utils/inspector-learning.d.cts.map +1 -0
  53. package/dist/utils/inspector-learning.d.mts +91 -0
  54. package/dist/utils/inspector-learning.d.mts.map +1 -0
  55. package/dist/utils/inspector-learning.mjs +182 -0
  56. package/dist/utils/inspector-learning.mjs.map +1 -0
  57. package/dist/utils/types.cjs.map +1 -1
  58. package/dist/utils/types.d.cts +13 -2
  59. package/dist/utils/types.d.cts.map +1 -1
  60. package/dist/utils/types.d.mts +13 -2
  61. package/dist/utils/types.d.mts.map +1 -1
  62. package/dist/utils/types.mjs.map +1 -1
  63. package/package.json +12 -1
  64. package/src/__tests__/license-context.test.ts +22 -1
  65. package/src/__tests__/root-entry-browser-safety.test.ts +85 -0
  66. package/src/attachments/types.ts +10 -0
  67. package/src/index.ts +27 -2
  68. package/src/telemetry/telemetry-client.ts +2 -17
  69. package/src/telemetry/telemetry-disabled.ts +22 -0
  70. package/src/utils/console-styling.ts +3 -3
  71. package/src/utils/index.ts +1 -0
  72. package/src/utils/inspector-learning.test.ts +213 -0
  73. package/src/utils/inspector-learning.ts +423 -0
  74. package/src/utils/types.ts +16 -1
  75. package/tsdown.config.ts +4 -1
@@ -0,0 +1,213 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ parseInspectorLearningRequestV1,
4
+ parseInspectorLearningSnapshotV1,
5
+ parseInspectorLearningUrl,
6
+ } from "./inspector-learning";
7
+
8
+ const emptySnapshot = () => ({
9
+ schemaVersion: 1,
10
+ projectKey: "project-safe-key",
11
+ snapshotVersion: "snapshot-1",
12
+ webAppOrigin: "https://app.copilotkit.ai",
13
+ configuration: { state: "not_configured" },
14
+ pendingThreadCount: 0,
15
+ run: { hasActiveRun: false, hasEverSucceeded: false, latest: null },
16
+ pendingCandidateCount: 0,
17
+ skillsPage: {
18
+ page: 1,
19
+ pageSize: 3,
20
+ total: 0,
21
+ totalPages: 0,
22
+ items: [],
23
+ },
24
+ insightsPage: {
25
+ page: 1,
26
+ pageSize: 4,
27
+ total: 0,
28
+ totalPages: 0,
29
+ items: [],
30
+ },
31
+ links: {
32
+ learning: "https://app.copilotkit.ai/learning",
33
+ candidates: null,
34
+ runs: null,
35
+ },
36
+ });
37
+
38
+ describe("Inspector Learning wire validation", () => {
39
+ it("accepts and copies a bounded V1 snapshot", () => {
40
+ const source = emptySnapshot();
41
+ const parsed = parseInspectorLearningSnapshotV1(source);
42
+
43
+ expect(parsed).toEqual(source);
44
+ expect(parsed).not.toBe(source);
45
+ });
46
+
47
+ it("copies only the supported fields from the latest Learning run", () => {
48
+ const source = {
49
+ ...emptySnapshot(),
50
+ run: {
51
+ hasActiveRun: false,
52
+ hasEverSucceeded: true,
53
+ latest: {
54
+ status: "succeeded",
55
+ completedAt: "2026-09-04T12:00:00.000Z",
56
+ internalPayload: "must-not-cross-the-boundary",
57
+ },
58
+ },
59
+ };
60
+
61
+ expect(parseInspectorLearningSnapshotV1(source)?.run.latest).toEqual({
62
+ status: "succeeded",
63
+ completedAt: "2026-09-04T12:00:00.000Z",
64
+ });
65
+ });
66
+
67
+ it("rejects unsafe links and oversized evidence", () => {
68
+ expect(
69
+ parseInspectorLearningUrl(
70
+ "javascript:alert(1)",
71
+ "https://app.copilotkit.ai",
72
+ ),
73
+ ).toBeUndefined();
74
+ expect(
75
+ parseInspectorLearningUrl(
76
+ "http://example.com/learning",
77
+ "https://app.copilotkit.ai",
78
+ ),
79
+ ).toBeUndefined();
80
+ expect(
81
+ parseInspectorLearningUrl(
82
+ "https://attacker.example/learning",
83
+ "https://app.copilotkit.ai",
84
+ ),
85
+ ).toBeUndefined();
86
+ expect(
87
+ parseInspectorLearningUrl(
88
+ "https://app.copilotkit.ai.evil.test/learning",
89
+ "https://app.copilotkit.ai",
90
+ ),
91
+ ).toBeUndefined();
92
+ expect(
93
+ parseInspectorLearningUrl(
94
+ "http://localhost:3000/learning",
95
+ "http://localhost:3000",
96
+ ),
97
+ ).toBe("http://localhost:3000/learning");
98
+ expect(
99
+ parseInspectorLearningUrl(
100
+ "https://intelligence.customer.example/learning",
101
+ "https://intelligence.customer.example",
102
+ ),
103
+ ).toBe("https://intelligence.customer.example/learning");
104
+
105
+ const snapshot = emptySnapshot();
106
+ snapshot.insightsPage = {
107
+ page: 1,
108
+ pageSize: 4,
109
+ total: 1,
110
+ totalPages: 1,
111
+ items: [
112
+ {
113
+ id: "insight-1",
114
+ statement: "A bounded pattern",
115
+ impact: "A bounded impact",
116
+ totalThreadCount: 101,
117
+ evidenceTruncated: true,
118
+ evidence: Array.from({ length: 101 }, (_, index) => ({
119
+ status: "available",
120
+ threadId: `thread-${index}`,
121
+ threadName: null,
122
+ messageIds: [],
123
+ updatedAt: "2026-09-03T00:00:00.000Z",
124
+ })),
125
+ },
126
+ ],
127
+ };
128
+ expect(parseInspectorLearningSnapshotV1(snapshot)).toBeUndefined();
129
+ });
130
+
131
+ it("rejects required actions that are absent or point at another origin", () => {
132
+ const missingRuns = emptySnapshot();
133
+ missingRuns.pendingThreadCount = 1;
134
+ expect(parseInspectorLearningSnapshotV1(missingRuns)).toBeUndefined();
135
+
136
+ const missingCandidates = emptySnapshot();
137
+ missingCandidates.pendingCandidateCount = 1;
138
+ expect(parseInspectorLearningSnapshotV1(missingCandidates)).toBeUndefined();
139
+
140
+ const arbitraryOrigin = emptySnapshot();
141
+ arbitraryOrigin.links.learning = "https://attacker.example/learning";
142
+ expect(parseInspectorLearningSnapshotV1(arbitraryOrigin)).toBeUndefined();
143
+
144
+ const configuredOrigin = emptySnapshot();
145
+ configuredOrigin.webAppOrigin = "https://intelligence.customer.example";
146
+ configuredOrigin.links.learning =
147
+ "https://intelligence.customer.example/learning";
148
+ expect(parseInspectorLearningSnapshotV1(configuredOrigin)).toBeDefined();
149
+ });
150
+
151
+ it("accepts identifier-free unavailable evidence separately from unnamed evidence", () => {
152
+ const snapshot = emptySnapshot();
153
+ snapshot.insightsPage = {
154
+ page: 1,
155
+ pageSize: 4,
156
+ total: 1,
157
+ totalPages: 1,
158
+ items: [
159
+ {
160
+ id: "insight-1",
161
+ statement: "Verify the order.",
162
+ impact: "Avoids incorrect guidance.",
163
+ totalThreadCount: 2,
164
+ evidenceTruncated: false,
165
+ evidence: [
166
+ { status: "unavailable" },
167
+ {
168
+ status: "available",
169
+ threadId: "thread-2",
170
+ threadName: null,
171
+ messageIds: ["message-2"],
172
+ updatedAt: "2026-09-03T00:00:00.000Z",
173
+ },
174
+ ],
175
+ },
176
+ ],
177
+ };
178
+
179
+ expect(parseInspectorLearningSnapshotV1(snapshot)).toBeDefined();
180
+ snapshot.insightsPage.items[0].evidence[0] = {
181
+ status: "unavailable",
182
+ threadId: "former-id",
183
+ };
184
+ expect(parseInspectorLearningSnapshotV1(snapshot)).toBeUndefined();
185
+ });
186
+
187
+ it("rejects inconsistent server pagination", () => {
188
+ const snapshot = emptySnapshot();
189
+ snapshot.insightsPage = {
190
+ page: 2,
191
+ pageSize: 4,
192
+ total: 1,
193
+ totalPages: 1,
194
+ items: [],
195
+ };
196
+
197
+ expect(parseInspectorLearningSnapshotV1(snapshot)).toBeUndefined();
198
+ });
199
+
200
+ it("accepts only the browser-owned scope and pagination fields", () => {
201
+ expect(
202
+ parseInspectorLearningRequestV1({
203
+ agentId: "support",
204
+ skillsPage: 2,
205
+ insightsPage: 3,
206
+ }),
207
+ ).toEqual({ agentId: "support", skillsPage: 2, insightsPage: 3 });
208
+ expect(
209
+ parseInspectorLearningRequestV1({ runtimeContainerId: "attacker-scope" }),
210
+ ).toBeUndefined();
211
+ expect(parseInspectorLearningRequestV1({ skillsPage: 0 })).toBeUndefined();
212
+ });
213
+ });
@@ -0,0 +1,423 @@
1
+ /** A bounded page in the Inspector Learning snapshot. */
2
+ export interface InspectorLearningPage<T> {
3
+ readonly page: number;
4
+ readonly pageSize: number;
5
+ readonly total: number;
6
+ readonly totalPages: number;
7
+ readonly items: readonly T[];
8
+ }
9
+
10
+ export type InspectorLearningEvidence =
11
+ | {
12
+ readonly status: "available";
13
+ readonly threadId: string;
14
+ readonly threadName: string | null;
15
+ readonly messageIds: readonly string[];
16
+ readonly updatedAt: string;
17
+ }
18
+ | { readonly status: "unavailable" };
19
+
20
+ export interface InspectorLearningInsight {
21
+ readonly id: string;
22
+ readonly statement: string;
23
+ readonly impact: string;
24
+ readonly totalThreadCount: number;
25
+ readonly evidenceTruncated: boolean;
26
+ readonly evidence: readonly InspectorLearningEvidence[];
27
+ }
28
+
29
+ export interface InspectorLearningSkill {
30
+ readonly id: string;
31
+ readonly name: string;
32
+ readonly description: string;
33
+ readonly revision: number;
34
+ readonly skillMd: string;
35
+ readonly sourceInsight: InspectorLearningInsight | null;
36
+ }
37
+
38
+ export interface InspectorLearningSnapshotV1 {
39
+ readonly schemaVersion: 1;
40
+ readonly projectKey: string;
41
+ readonly snapshotVersion: string;
42
+ readonly webAppOrigin: string;
43
+ readonly configuration:
44
+ | { readonly state: "not_configured" }
45
+ | {
46
+ readonly state: "invalid";
47
+ readonly reason: "container" | "instrumentation";
48
+ }
49
+ | {
50
+ readonly state: "configured";
51
+ readonly container: { readonly id: string; readonly name: string };
52
+ }
53
+ | { readonly state: "selection_required" };
54
+ readonly pendingThreadCount: number;
55
+ readonly run: {
56
+ readonly hasActiveRun: boolean;
57
+ readonly hasEverSucceeded: boolean;
58
+ readonly latest: {
59
+ readonly completedAt: string | null;
60
+ readonly status:
61
+ | "queued"
62
+ | "freezing"
63
+ | "batching"
64
+ | "reducing"
65
+ | "finalizing"
66
+ | "succeeded"
67
+ | "failed";
68
+ } | null;
69
+ };
70
+ readonly pendingCandidateCount: number;
71
+ readonly skillsPage: InspectorLearningPage<InspectorLearningSkill>;
72
+ readonly insightsPage: InspectorLearningPage<InspectorLearningInsight>;
73
+ readonly links: {
74
+ readonly learning: string;
75
+ readonly candidates: string | null;
76
+ readonly runs: string | null;
77
+ };
78
+ }
79
+
80
+ export interface InspectorLearningRequestV1 {
81
+ readonly agentId?: string;
82
+ readonly skillsPage?: number;
83
+ readonly insightsPage?: number;
84
+ }
85
+
86
+ type RecordValue = Record<string, unknown>;
87
+
88
+ const record = (value: unknown): value is RecordValue =>
89
+ typeof value === "object" && value !== null && !Array.isArray(value);
90
+ const string = (value: unknown, maximum: number): value is string =>
91
+ typeof value === "string" &&
92
+ value.trim().length > 0 &&
93
+ value.length <= maximum;
94
+ const integer = (value: unknown, minimum = 0): value is number =>
95
+ typeof value === "number" && Number.isSafeInteger(value) && value >= minimum;
96
+ const date = (value: unknown): value is string =>
97
+ typeof value === "string" && !Number.isNaN(Date.parse(value));
98
+
99
+ /**
100
+ * Accepts only the configured Intelligence web-app origin.
101
+ *
102
+ * Learning links are server-authored actions. Treating every HTTPS origin as
103
+ * trusted would let a misconfigured or compromised upstream turn Inspector
104
+ * copy into a navigation primitive for an arbitrary site.
105
+ */
106
+ export function parseInspectorLearningUrl(
107
+ value: unknown,
108
+ trustedOrigin: unknown,
109
+ ): string | undefined {
110
+ if (
111
+ typeof value !== "string" ||
112
+ value.length > 4_000 ||
113
+ typeof trustedOrigin !== "string" ||
114
+ trustedOrigin.length > 2_000
115
+ ) {
116
+ return undefined;
117
+ }
118
+ try {
119
+ const parsed = new URL(value);
120
+ const configured = new URL(trustedOrigin);
121
+ const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(
122
+ configured.hostname,
123
+ );
124
+ if (
125
+ parsed.username ||
126
+ parsed.password ||
127
+ configured.username ||
128
+ configured.password ||
129
+ configured.pathname !== "/" ||
130
+ configured.search ||
131
+ configured.hash ||
132
+ (configured.protocol !== "https:" &&
133
+ !(configured.protocol === "http:" && loopback)) ||
134
+ parsed.origin !== configured.origin
135
+ ) {
136
+ return undefined;
137
+ }
138
+ return parsed.toString();
139
+ } catch {
140
+ return undefined;
141
+ }
142
+ }
143
+
144
+ function parseEvidence(value: unknown): InspectorLearningEvidence | undefined {
145
+ if (record(value) && value.status === "unavailable") {
146
+ return Object.keys(value).length === 1
147
+ ? { status: "unavailable" }
148
+ : undefined;
149
+ }
150
+ if (
151
+ !record(value) ||
152
+ value.status !== "available" ||
153
+ !string(value.threadId, 128) ||
154
+ !date(value.updatedAt)
155
+ ) {
156
+ return undefined;
157
+ }
158
+ if (
159
+ value.threadName !== null &&
160
+ !(typeof value.threadName === "string" && value.threadName.length <= 4_000)
161
+ ) {
162
+ return undefined;
163
+ }
164
+ if (
165
+ !Array.isArray(value.messageIds) ||
166
+ value.messageIds.length > 200 ||
167
+ !value.messageIds.every((id) => string(id, 128))
168
+ ) {
169
+ return undefined;
170
+ }
171
+ return {
172
+ status: "available",
173
+ threadId: value.threadId,
174
+ threadName: value.threadName,
175
+ messageIds: [...value.messageIds],
176
+ updatedAt: value.updatedAt,
177
+ };
178
+ }
179
+
180
+ function parseInsight(value: unknown): InspectorLearningInsight | undefined {
181
+ if (
182
+ !record(value) ||
183
+ !string(value.id, 128) ||
184
+ !string(value.statement, 4_000) ||
185
+ !string(value.impact, 4_000) ||
186
+ !integer(value.totalThreadCount) ||
187
+ typeof value.evidenceTruncated !== "boolean" ||
188
+ !Array.isArray(value.evidence) ||
189
+ value.evidence.length > 100
190
+ ) {
191
+ return undefined;
192
+ }
193
+ const evidence = value.evidence.map(parseEvidence);
194
+ if (evidence.some((item) => item === undefined)) return undefined;
195
+ return {
196
+ id: value.id,
197
+ statement: value.statement,
198
+ impact: value.impact,
199
+ totalThreadCount: value.totalThreadCount,
200
+ evidenceTruncated: value.evidenceTruncated,
201
+ evidence: evidence as InspectorLearningEvidence[],
202
+ };
203
+ }
204
+
205
+ function parseSkill(value: unknown): InspectorLearningSkill | undefined {
206
+ if (
207
+ !record(value) ||
208
+ !string(value.id, 128) ||
209
+ !string(value.name, 128) ||
210
+ !string(value.description, 4_000) ||
211
+ !integer(value.revision, 1) ||
212
+ typeof value.skillMd !== "string" ||
213
+ value.skillMd.length > 131_072
214
+ ) {
215
+ return undefined;
216
+ }
217
+ const sourceInsight =
218
+ value.sourceInsight === null ? null : parseInsight(value.sourceInsight);
219
+ if (sourceInsight === undefined) return undefined;
220
+ return {
221
+ id: value.id,
222
+ name: value.name,
223
+ description: value.description,
224
+ revision: value.revision,
225
+ skillMd: value.skillMd,
226
+ sourceInsight,
227
+ };
228
+ }
229
+
230
+ function parsePage<T>(
231
+ value: unknown,
232
+ pageSize: 3 | 4,
233
+ parseItem: (item: unknown) => T | undefined,
234
+ ): InspectorLearningPage<T> | undefined {
235
+ if (
236
+ !record(value) ||
237
+ !integer(value.page, 1) ||
238
+ value.pageSize !== pageSize ||
239
+ !integer(value.total) ||
240
+ !integer(value.totalPages) ||
241
+ !Array.isArray(value.items) ||
242
+ value.items.length > pageSize
243
+ ) {
244
+ return undefined;
245
+ }
246
+ const items = value.items.map(parseItem);
247
+ if (items.some((item) => item === undefined)) return undefined;
248
+ if (value.total === 0) {
249
+ if (value.page !== 1 || value.totalPages !== 0 || items.length !== 0) {
250
+ return undefined;
251
+ }
252
+ } else {
253
+ const totalPages = Math.ceil(value.total / pageSize);
254
+ const expectedItems = Math.min(
255
+ pageSize,
256
+ value.total - (value.page - 1) * pageSize,
257
+ );
258
+ if (
259
+ value.totalPages !== totalPages ||
260
+ value.page > totalPages ||
261
+ items.length !== expectedItems
262
+ ) {
263
+ return undefined;
264
+ }
265
+ }
266
+ return {
267
+ page: value.page,
268
+ pageSize,
269
+ total: value.total,
270
+ totalPages: value.totalPages,
271
+ items: items as T[],
272
+ };
273
+ }
274
+
275
+ function parseConfiguration(
276
+ value: unknown,
277
+ ): InspectorLearningSnapshotV1["configuration"] | undefined {
278
+ if (!record(value)) return undefined;
279
+ if (
280
+ value.state === "not_configured" ||
281
+ value.state === "selection_required"
282
+ ) {
283
+ return { state: value.state };
284
+ }
285
+ if (
286
+ value.state === "invalid" &&
287
+ (value.reason === "container" || value.reason === "instrumentation")
288
+ ) {
289
+ return { state: value.state, reason: value.reason };
290
+ }
291
+ if (
292
+ value.state === "configured" &&
293
+ record(value.container) &&
294
+ string(value.container.id, 128) &&
295
+ string(value.container.name, 128)
296
+ ) {
297
+ return {
298
+ state: "configured",
299
+ container: { id: value.container.id, name: value.container.name },
300
+ };
301
+ }
302
+ return undefined;
303
+ }
304
+
305
+ const runStatuses = new Set([
306
+ "queued",
307
+ "freezing",
308
+ "batching",
309
+ "reducing",
310
+ "finalizing",
311
+ "succeeded",
312
+ "failed",
313
+ ]);
314
+
315
+ /** Validates and copies an untrusted Learning snapshot at each process boundary. */
316
+ export function parseInspectorLearningSnapshotV1(
317
+ value: unknown,
318
+ ): InspectorLearningSnapshotV1 | undefined {
319
+ if (
320
+ !record(value) ||
321
+ value.schemaVersion !== 1 ||
322
+ !string(value.projectKey, 128) ||
323
+ !string(value.snapshotVersion, 128) ||
324
+ !integer(value.pendingThreadCount) ||
325
+ !integer(value.pendingCandidateCount) ||
326
+ !record(value.run) ||
327
+ typeof value.run.hasActiveRun !== "boolean" ||
328
+ typeof value.run.hasEverSucceeded !== "boolean"
329
+ ) {
330
+ return undefined;
331
+ }
332
+ const configuration = parseConfiguration(value.configuration);
333
+ const skillsPage = parsePage(value.skillsPage, 3, parseSkill);
334
+ const insightsPage = parsePage(value.insightsPage, 4, parseInsight);
335
+ if (!configuration || !skillsPage || !insightsPage) return undefined;
336
+ let latest: InspectorLearningSnapshotV1["run"]["latest"] = null;
337
+ if (value.run.latest !== null) {
338
+ const latestRun = value.run.latest;
339
+ if (!record(latestRun)) {
340
+ return undefined;
341
+ }
342
+ const { status, completedAt } = latestRun;
343
+ if (
344
+ typeof status !== "string" ||
345
+ !runStatuses.has(status) ||
346
+ (completedAt !== null && !date(completedAt))
347
+ )
348
+ return undefined;
349
+ latest = {
350
+ status: status as NonNullable<typeof latest>["status"],
351
+ completedAt: completedAt as NonNullable<typeof latest>["completedAt"],
352
+ };
353
+ }
354
+ if (!record(value.links)) return undefined;
355
+ const originUrl = parseInspectorLearningUrl(
356
+ value.webAppOrigin,
357
+ value.webAppOrigin,
358
+ );
359
+ if (!originUrl || new URL(originUrl).pathname !== "/") return undefined;
360
+ const webAppOrigin = new URL(originUrl).origin;
361
+ const learning = parseInspectorLearningUrl(
362
+ value.links.learning,
363
+ webAppOrigin,
364
+ );
365
+ const candidates =
366
+ value.links.candidates === null
367
+ ? null
368
+ : parseInspectorLearningUrl(value.links.candidates, webAppOrigin);
369
+ const runs =
370
+ value.links.runs === null
371
+ ? null
372
+ : parseInspectorLearningUrl(value.links.runs, webAppOrigin);
373
+ if (
374
+ !learning ||
375
+ candidates === undefined ||
376
+ runs === undefined ||
377
+ (value.pendingThreadCount > 0 && runs === null) ||
378
+ (value.pendingCandidateCount > 0 && candidates === null)
379
+ )
380
+ return undefined;
381
+ return {
382
+ schemaVersion: 1,
383
+ projectKey: value.projectKey,
384
+ snapshotVersion: value.snapshotVersion,
385
+ webAppOrigin,
386
+ configuration,
387
+ pendingThreadCount: value.pendingThreadCount,
388
+ run: {
389
+ hasActiveRun: value.run.hasActiveRun,
390
+ hasEverSucceeded: value.run.hasEverSucceeded,
391
+ latest,
392
+ },
393
+ pendingCandidateCount: value.pendingCandidateCount,
394
+ skillsPage,
395
+ insightsPage,
396
+ links: { learning, candidates, runs },
397
+ };
398
+ }
399
+
400
+ /** Normalizes the browser-owned request without accepting container scope. */
401
+ export function parseInspectorLearningRequestV1(
402
+ value: unknown,
403
+ ): InspectorLearningRequestV1 | undefined {
404
+ if (!record(value)) return undefined;
405
+ if (
406
+ Object.keys(value).some(
407
+ (key) => !["agentId", "skillsPage", "insightsPage"].includes(key),
408
+ )
409
+ ) {
410
+ return undefined;
411
+ }
412
+ const agentId = value.agentId;
413
+ const skillsPage = value.skillsPage;
414
+ const insightsPage = value.insightsPage;
415
+ if (agentId !== undefined && !string(agentId, 128)) return undefined;
416
+ if (skillsPage !== undefined && !integer(skillsPage, 1)) return undefined;
417
+ if (insightsPage !== undefined && !integer(insightsPage, 1)) return undefined;
418
+ return {
419
+ ...(agentId === undefined ? {} : { agentId }),
420
+ ...(skillsPage === undefined ? {} : { skillsPage }),
421
+ ...(insightsPage === undefined ? {} : { insightsPage }),
422
+ };
423
+ }
@@ -51,7 +51,10 @@ interface RuntimeEntitlement {
51
51
  /** Whether the resolved entitlement currently grants product access. */
52
52
  active: boolean;
53
53
  /** Deployment authority that produced this entitlement. */
54
- source: "managedOrgSubscription" | "selfHostedDeploymentLicense";
54
+ source:
55
+ | "managedOrgSubscription"
56
+ | "selfHostedDeploymentLicense"
57
+ | "awsMarketplaceDeploymentLicense";
55
58
  /** Boolean feature grants keyed by stable feature id. */
56
59
  features: Record<string, boolean>;
57
60
  /** Numeric limits keyed by stable feature id. */
@@ -104,6 +107,14 @@ export interface A2UIRuntimeInfo {
104
107
  agents?: string[];
105
108
  }
106
109
 
110
+ /** Intelligence resource support available through a single runtime route. */
111
+ export interface SingleRouteRuntimeInfo {
112
+ /** Whether the runtime accepts resource requests through the single route. */
113
+ resourceOperations: boolean;
114
+ /** Thread features available through the single-route resource bridge. */
115
+ threadEndpoints: ThreadEndpointRuntimeInfo;
116
+ }
117
+
107
118
  export interface RuntimeInfo {
108
119
  version: string;
109
120
  agents: Record<string, AgentDescription>;
@@ -111,8 +122,12 @@ export interface RuntimeInfo {
111
122
  mode: RuntimeMode;
112
123
  intelligence?: IntelligenceRuntimeInfo;
113
124
  threadEndpoints?: ThreadEndpointRuntimeInfo;
125
+ /** Optional capabilities exposed by runtimes that use one HTTP route. */
126
+ singleRoute?: SingleRouteRuntimeInfo;
114
127
  /** Whether this runtime exposes trusted inspector metadata. */
115
128
  inspectorMetadata?: boolean;
129
+ /** Whether this runtime exposes the debug-authorized Learning snapshot. */
130
+ inspectorLearning?: boolean;
116
131
  /**
117
132
  * When true, the runtime exposes POST /agent/:agentId/suggest for stateless
118
133
  * suggestion generation. Absent on older runtimes; clients fall back to a
package/tsdown.config.ts CHANGED
@@ -2,7 +2,10 @@ import { defineConfig } from "tsdown";
2
2
 
3
3
  export default defineConfig([
4
4
  {
5
- entry: ["src/index.ts"],
5
+ // `src/telemetry/index.ts` is a second entry so that the Node-only
6
+ // telemetry client is reachable as `@copilotkit/shared/telemetry`. The
7
+ // root entry deliberately does not re-export it (#4151).
8
+ entry: ["src/index.ts", "src/telemetry/index.ts"],
6
9
  format: ["esm", "cjs"],
7
10
  dts: true,
8
11
  sourcemap: true,