@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.
- package/dist/attachments/types.d.cts +10 -0
- package/dist/attachments/types.d.cts.map +1 -1
- package/dist/attachments/types.d.mts +10 -0
- package/dist/attachments/types.d.mts.map +1 -1
- package/dist/index.cjs +8 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +5 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5 -4
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +229 -200
- package/dist/index.umd.js.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/telemetry/index.cjs +15 -0
- package/dist/telemetry/index.d.cts +5 -0
- package/dist/telemetry/index.d.mts +4 -2
- package/dist/telemetry/index.mjs +6 -0
- package/dist/telemetry/telemetry-client.cjs +4 -13
- package/dist/telemetry/telemetry-client.cjs.map +1 -1
- package/dist/telemetry/telemetry-client.d.cts +2 -8
- package/dist/telemetry/telemetry-client.d.cts.map +1 -1
- package/dist/telemetry/telemetry-client.d.mts +2 -8
- package/dist/telemetry/telemetry-client.d.mts.map +1 -1
- package/dist/telemetry/telemetry-client.mjs +4 -12
- package/dist/telemetry/telemetry-client.mjs.map +1 -1
- package/dist/telemetry/telemetry-disabled.cjs +20 -0
- package/dist/telemetry/telemetry-disabled.cjs.map +1 -0
- package/dist/telemetry/telemetry-disabled.d.cts +16 -0
- package/dist/telemetry/telemetry-disabled.d.cts.map +1 -0
- package/dist/telemetry/telemetry-disabled.d.mts +16 -0
- package/dist/telemetry/telemetry-disabled.d.mts.map +1 -0
- package/dist/telemetry/telemetry-disabled.mjs +19 -0
- package/dist/telemetry/telemetry-disabled.mjs.map +1 -0
- 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.cjs +1 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.cts +2 -1
- package/dist/utils/index.d.cts.map +1 -1
- package/dist/utils/index.d.mts +2 -1
- package/dist/utils/index.d.mts.map +1 -1
- package/dist/utils/index.mjs +1 -0
- package/dist/utils/index.mjs.map +1 -1
- package/dist/utils/inspector-learning.cjs +185 -0
- package/dist/utils/inspector-learning.cjs.map +1 -0
- package/dist/utils/inspector-learning.d.cts +91 -0
- package/dist/utils/inspector-learning.d.cts.map +1 -0
- package/dist/utils/inspector-learning.d.mts +91 -0
- package/dist/utils/inspector-learning.d.mts.map +1 -0
- package/dist/utils/inspector-learning.mjs +182 -0
- package/dist/utils/inspector-learning.mjs.map +1 -0
- package/dist/utils/types.cjs.map +1 -1
- package/dist/utils/types.d.cts +13 -2
- package/dist/utils/types.d.cts.map +1 -1
- package/dist/utils/types.d.mts +13 -2
- package/dist/utils/types.d.mts.map +1 -1
- package/dist/utils/types.mjs.map +1 -1
- package/package.json +12 -1
- package/src/__tests__/license-context.test.ts +22 -1
- package/src/__tests__/root-entry-browser-safety.test.ts +85 -0
- package/src/attachments/types.ts +10 -0
- package/src/index.ts +27 -2
- package/src/telemetry/telemetry-client.ts +2 -17
- package/src/telemetry/telemetry-disabled.ts +22 -0
- package/src/utils/console-styling.ts +3 -3
- package/src/utils/index.ts +1 -0
- package/src/utils/inspector-learning.test.ts +213 -0
- package/src/utils/inspector-learning.ts +423 -0
- package/src/utils/types.ts +16 -1
- 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
|
+
}
|
package/src/utils/types.ts
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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,
|