@copilotkit/web-inspector 1.55.2 → 1.55.3-canary.1776215089
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 +22 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +22 -8
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +22 -8
- package/dist/index.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/web-inspector.spec.ts +112 -35
- package/src/index.ts +41 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@copilotkit/web-inspector",
|
|
3
|
-
"version": "1.55.
|
|
3
|
+
"version": "1.55.3-canary.1776215089",
|
|
4
4
|
"description": "Lit-based web component for the CopilotKit web inspector",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"lit": "^3.2.0",
|
|
24
24
|
"lucide": "^0.525.0",
|
|
25
25
|
"marked": "^12.0.2",
|
|
26
|
-
"@copilotkit/core": "1.55.
|
|
26
|
+
"@copilotkit/core": "1.55.3-canary.1776215089"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@tailwindcss/cli": "^4.1.11",
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
import type { AbstractAgent, AgentSubscriber } from "@ag-ui/client";
|
|
8
8
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
// --- Types for accessing LitElement-private reactive properties ---
|
|
11
|
+
// WebInspectorElement stores these as private Lit reactive properties.
|
|
12
|
+
// There's no public API to read them, so the cast is unavoidable in tests.
|
|
13
13
|
|
|
14
14
|
type InspectorInternals = {
|
|
15
15
|
flattenedEvents: Array<{ type: string }>;
|
|
@@ -24,6 +24,8 @@ type InspectorContextInternals = {
|
|
|
24
24
|
persistState: () => void;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
// --- Mock agent factory ---
|
|
28
|
+
|
|
27
29
|
type MockAgentExtras = Partial<{
|
|
28
30
|
messages: unknown;
|
|
29
31
|
state: unknown;
|
|
@@ -31,13 +33,21 @@ type MockAgentExtras = Partial<{
|
|
|
31
33
|
toolRenderers: Record<string, unknown>;
|
|
32
34
|
}>;
|
|
33
35
|
|
|
36
|
+
type MockAgentController = {
|
|
37
|
+
// Each subscriber method has a different parameter shape — TypeScript
|
|
38
|
+
// can't narrow a dynamic key lookup, so the internal cast is unavoidable.
|
|
39
|
+
emit: (key: keyof AgentSubscriber, payload: unknown) => void;
|
|
40
|
+
/** Simulate AbstractAgent.setState(): mutate the mock's state and notify subscribers. */
|
|
41
|
+
simulateSetState: (newState: Record<string, unknown>) => void;
|
|
42
|
+
};
|
|
43
|
+
|
|
34
44
|
function createMockAgent(
|
|
35
45
|
agentId: string,
|
|
36
46
|
extras: MockAgentExtras = {},
|
|
37
47
|
): { agent: AbstractAgent; controller: MockAgentController } {
|
|
38
48
|
const subscribers = new Set<AgentSubscriber>();
|
|
39
49
|
|
|
40
|
-
const
|
|
50
|
+
const agentObj = {
|
|
41
51
|
agentId,
|
|
42
52
|
...extras,
|
|
43
53
|
subscribe(subscriber: AgentSubscriber) {
|
|
@@ -57,9 +67,25 @@ function createMockAgent(
|
|
|
57
67
|
});
|
|
58
68
|
};
|
|
59
69
|
|
|
60
|
-
|
|
70
|
+
const simulateSetState = (newState: Record<string, unknown>) => {
|
|
71
|
+
agentObj.state = newState;
|
|
72
|
+
emit("onStateChanged", {
|
|
73
|
+
state: newState,
|
|
74
|
+
messages: agentObj.messages ?? [],
|
|
75
|
+
agent: agentObj,
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// AbstractAgent is an abstract class — our plain-object mock satisfies
|
|
80
|
+
// the subset the inspector uses but can't extend the class.
|
|
81
|
+
return {
|
|
82
|
+
agent: agentObj as unknown as AbstractAgent,
|
|
83
|
+
controller: { emit, simulateSetState },
|
|
84
|
+
};
|
|
61
85
|
}
|
|
62
86
|
|
|
87
|
+
// --- Mock core factory ---
|
|
88
|
+
|
|
63
89
|
type MockCore = {
|
|
64
90
|
agents: Record<string, AbstractAgent>;
|
|
65
91
|
context: Record<string, unknown>;
|
|
@@ -87,6 +113,8 @@ function createMockCore(initialAgents: Record<string, AbstractAgent> = {}) {
|
|
|
87
113
|
core,
|
|
88
114
|
emitAgentsChanged(nextAgents = core.agents) {
|
|
89
115
|
core.agents = nextAgents;
|
|
116
|
+
// CopilotKitCore is a full class — our mock only covers what the
|
|
117
|
+
// inspector reads, so this cast is unavoidable.
|
|
90
118
|
subscribers.forEach((subscriber) =>
|
|
91
119
|
subscriber.onAgentsChanged?.({
|
|
92
120
|
copilotkit: core as unknown as CopilotKitCore,
|
|
@@ -108,7 +136,33 @@ function createMockCore(initialAgents: Record<string, AbstractAgent> = {}) {
|
|
|
108
136
|
};
|
|
109
137
|
}
|
|
110
138
|
|
|
139
|
+
// --- Test helpers ---
|
|
140
|
+
|
|
141
|
+
/** Create inspector, attach to DOM, wire up mock core. */
|
|
142
|
+
function createInspectorWithCore(core: MockCore) {
|
|
143
|
+
const inspector = new WebInspectorElement();
|
|
144
|
+
document.body.appendChild(inspector);
|
|
145
|
+
// WebInspectorElement["core"] is a CopilotKitCore instance — our MockCore
|
|
146
|
+
// only implements the subset exercised by these tests.
|
|
147
|
+
inspector.core = core as unknown as WebInspectorElement["core"];
|
|
148
|
+
return inspector;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Access private Lit reactive properties on the inspector. */
|
|
152
|
+
function getInternals(inspector: WebInspectorElement) {
|
|
153
|
+
return inspector as unknown as InspectorInternals;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Access context-related private properties on the inspector. */
|
|
157
|
+
function getContextInternals(inspector: WebInspectorElement) {
|
|
158
|
+
return inspector as unknown as InspectorContextInternals;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// --- Tests ---
|
|
162
|
+
|
|
111
163
|
describe("WebInspectorElement", () => {
|
|
164
|
+
let mockClipboard: { writeText: ReturnType<typeof vi.fn> };
|
|
165
|
+
|
|
112
166
|
beforeEach(() => {
|
|
113
167
|
document.body.innerHTML = "";
|
|
114
168
|
|
|
@@ -130,7 +184,9 @@ describe("WebInspectorElement", () => {
|
|
|
130
184
|
key: (index: number) => Object.keys(store)[index] ?? null,
|
|
131
185
|
});
|
|
132
186
|
|
|
133
|
-
|
|
187
|
+
mockClipboard = { writeText: vi.fn().mockResolvedValue(undefined) };
|
|
188
|
+
// navigator.clipboard is readonly in DOM types — assigning
|
|
189
|
+
// the mock requires a cast in jsdom-style test environments.
|
|
134
190
|
(navigator as unknown as { clipboard: typeof mockClipboard }).clipboard =
|
|
135
191
|
mockClipboard;
|
|
136
192
|
});
|
|
@@ -148,10 +204,7 @@ describe("WebInspectorElement", () => {
|
|
|
148
204
|
},
|
|
149
205
|
});
|
|
150
206
|
const { core, emitAgentsChanged } = createMockCore({ alpha: agent });
|
|
151
|
-
|
|
152
|
-
const inspector = new WebInspectorElement();
|
|
153
|
-
document.body.appendChild(inspector);
|
|
154
|
-
inspector.core = core as unknown as WebInspectorElement["core"];
|
|
207
|
+
const inspector = createInspectorWithCore(core);
|
|
155
208
|
|
|
156
209
|
emitAgentsChanged();
|
|
157
210
|
await inspector.updateComplete;
|
|
@@ -160,27 +213,26 @@ describe("WebInspectorElement", () => {
|
|
|
160
213
|
controller.emit("onMessagesSnapshotEvent", { event: { id: "msg-1" } });
|
|
161
214
|
await inspector.updateComplete;
|
|
162
215
|
|
|
163
|
-
const
|
|
216
|
+
const internals = getInternals(inspector);
|
|
164
217
|
|
|
165
|
-
const flattened = inspectorHandle.flattenedEvents;
|
|
166
|
-
expect(flattened.some((evt) => evt.type === "RUN_STARTED")).toBe(true);
|
|
167
|
-
expect(flattened.some((evt) => evt.type === "MESSAGES_SNAPSHOT")).toBe(
|
|
168
|
-
true,
|
|
169
|
-
);
|
|
170
218
|
expect(
|
|
171
|
-
|
|
172
|
-
).
|
|
173
|
-
expect(inspectorHandle.agentStates.get("alpha")).toBeDefined();
|
|
219
|
+
internals.flattenedEvents.some((evt) => evt.type === "RUN_STARTED"),
|
|
220
|
+
).toBe(true);
|
|
174
221
|
expect(
|
|
175
|
-
|
|
222
|
+
internals.flattenedEvents.some((evt) => evt.type === "MESSAGES_SNAPSHOT"),
|
|
176
223
|
).toBe(true);
|
|
224
|
+
expect(internals.agentMessages.get("alpha")?.[0]?.contentText).toContain(
|
|
225
|
+
"hi there",
|
|
226
|
+
);
|
|
227
|
+
expect(internals.agentStates.get("alpha")).toBeDefined();
|
|
228
|
+
expect(internals.cachedTools.some((tool) => tool.name === "greet")).toBe(
|
|
229
|
+
true,
|
|
230
|
+
);
|
|
177
231
|
});
|
|
178
232
|
|
|
179
233
|
it("normalizes context, persists state, and copies context values", async () => {
|
|
180
234
|
const { core, emitContextChanged } = createMockCore();
|
|
181
|
-
const inspector =
|
|
182
|
-
document.body.appendChild(inspector);
|
|
183
|
-
inspector.core = core as unknown as WebInspectorElement["core"];
|
|
235
|
+
const inspector = createInspectorWithCore(core);
|
|
184
236
|
|
|
185
237
|
emitContextChanged({
|
|
186
238
|
ctxA: { value: { nested: true } },
|
|
@@ -188,22 +240,47 @@ describe("WebInspectorElement", () => {
|
|
|
188
240
|
});
|
|
189
241
|
await inspector.updateComplete;
|
|
190
242
|
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
const ctxB = contextStore.ctxB!;
|
|
243
|
+
const contextInternals = getContextInternals(inspector);
|
|
244
|
+
const ctxA = contextInternals.contextStore.ctxA!;
|
|
245
|
+
const ctxB = contextInternals.contextStore.ctxB!;
|
|
195
246
|
expect(ctxA.value).toMatchObject({ nested: true });
|
|
196
247
|
expect(ctxB.description).toBe("Described");
|
|
197
248
|
|
|
198
|
-
await
|
|
199
|
-
|
|
200
|
-
navigator as unknown as {
|
|
201
|
-
clipboard: { writeText: ReturnType<typeof vi.fn> };
|
|
202
|
-
}
|
|
203
|
-
).clipboard.writeText as ReturnType<typeof vi.fn>;
|
|
204
|
-
expect(clipboard).toHaveBeenCalledTimes(1);
|
|
249
|
+
await contextInternals.copyContextValue({ nested: true }, "ctxA");
|
|
250
|
+
expect(mockClipboard.writeText).toHaveBeenCalledTimes(1);
|
|
205
251
|
|
|
206
|
-
|
|
252
|
+
contextInternals.persistState();
|
|
207
253
|
expect(localStorage.getItem("cpk:inspector:state")).toBeTruthy();
|
|
208
254
|
});
|
|
255
|
+
|
|
256
|
+
it("syncs agent state on direct setState (onStateChanged without pipeline events)", async () => {
|
|
257
|
+
// Simulates a selfManagedAgent where agent.setState() is called directly
|
|
258
|
+
// from UI code, bypassing the AG-UI event pipeline. Before the fix,
|
|
259
|
+
// only pipeline event handlers (onStateSnapshotEvent, onStateDeltaEvent)
|
|
260
|
+
// updated the inspector — onStateChanged was not subscribed to, so
|
|
261
|
+
// direct setState() left the inspector stale.
|
|
262
|
+
const { agent, controller } = createMockAgent("counter", {
|
|
263
|
+
state: { counter: 0 },
|
|
264
|
+
});
|
|
265
|
+
const { core, emitAgentsChanged } = createMockCore({ counter: agent });
|
|
266
|
+
const inspector = createInspectorWithCore(core);
|
|
267
|
+
|
|
268
|
+
emitAgentsChanged();
|
|
269
|
+
await inspector.updateComplete;
|
|
270
|
+
|
|
271
|
+
const internals = getInternals(inspector);
|
|
272
|
+
|
|
273
|
+
// Initial state should be captured on subscription
|
|
274
|
+
expect(internals.agentStates.get("counter")).toEqual({ counter: 0 });
|
|
275
|
+
|
|
276
|
+
// Simulate agent.setState({ counter: 1 })
|
|
277
|
+
controller.simulateSetState({ counter: 1 });
|
|
278
|
+
await inspector.updateComplete;
|
|
279
|
+
expect(internals.agentStates.get("counter")).toEqual({ counter: 1 });
|
|
280
|
+
|
|
281
|
+
// Simulate a second setState to verify repeated updates propagate
|
|
282
|
+
controller.simulateSetState({ counter: 5 });
|
|
283
|
+
await inspector.updateComplete;
|
|
284
|
+
expect(internals.agentStates.get("counter")).toEqual({ counter: 5 });
|
|
285
|
+
});
|
|
209
286
|
});
|
package/src/index.ts
CHANGED
|
@@ -287,6 +287,14 @@ export class WebInspectorElement extends LitElement {
|
|
|
287
287
|
onAgentsChanged: ({ agents }) => {
|
|
288
288
|
this.processAgentsChanged(agents);
|
|
289
289
|
},
|
|
290
|
+
onAgentRunStarted: ({ agent }) => {
|
|
291
|
+
// Per-thread clones are not in the agent registry, so
|
|
292
|
+
// onAgentsChanged never fires for them. Subscribe here so
|
|
293
|
+
// the inspector captures their AG-UI events.
|
|
294
|
+
if (agent?.agentId) {
|
|
295
|
+
this.subscribeToAgent(agent);
|
|
296
|
+
}
|
|
297
|
+
},
|
|
290
298
|
onContextChanged: ({ context }) => {
|
|
291
299
|
this.contextStore = this.normalizeContextStore(context);
|
|
292
300
|
this.requestUpdate();
|
|
@@ -488,6 +496,9 @@ export class WebInspectorElement extends LitElement {
|
|
|
488
496
|
onMessagesChanged: () => {
|
|
489
497
|
this.syncAgentMessages(agent);
|
|
490
498
|
},
|
|
499
|
+
onStateChanged: () => {
|
|
500
|
+
this.syncAgentState(agent);
|
|
501
|
+
},
|
|
491
502
|
onRawEvent: ({ event }) => {
|
|
492
503
|
this.recordAgentEvent(agentId, "RAW_EVENT", event);
|
|
493
504
|
},
|
|
@@ -573,16 +584,23 @@ export class WebInspectorElement extends LitElement {
|
|
|
573
584
|
return;
|
|
574
585
|
}
|
|
575
586
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
587
|
+
try {
|
|
588
|
+
const messages = this.normalizeAgentMessages(
|
|
589
|
+
(agent as { messages?: unknown }).messages,
|
|
590
|
+
);
|
|
591
|
+
if (messages) {
|
|
592
|
+
this.agentMessages.set(agent.agentId, messages);
|
|
593
|
+
} else {
|
|
594
|
+
this.agentMessages.delete(agent.agentId);
|
|
595
|
+
}
|
|
584
596
|
|
|
585
|
-
|
|
597
|
+
this.requestUpdate();
|
|
598
|
+
} catch (error) {
|
|
599
|
+
console.error(
|
|
600
|
+
`[CopilotKit Inspector] Failed to sync messages for agent "${agent.agentId}":`,
|
|
601
|
+
error,
|
|
602
|
+
);
|
|
603
|
+
}
|
|
586
604
|
}
|
|
587
605
|
|
|
588
606
|
private syncAgentState(agent: AbstractAgent): void {
|
|
@@ -590,15 +608,22 @@ export class WebInspectorElement extends LitElement {
|
|
|
590
608
|
return;
|
|
591
609
|
}
|
|
592
610
|
|
|
593
|
-
|
|
611
|
+
try {
|
|
612
|
+
const state = (agent as { state?: unknown }).state;
|
|
594
613
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
614
|
+
if (state === undefined || state === null) {
|
|
615
|
+
this.agentStates.delete(agent.agentId);
|
|
616
|
+
} else {
|
|
617
|
+
this.agentStates.set(agent.agentId, this.sanitizeForLogging(state));
|
|
618
|
+
}
|
|
600
619
|
|
|
601
|
-
|
|
620
|
+
this.requestUpdate();
|
|
621
|
+
} catch (error) {
|
|
622
|
+
console.error(
|
|
623
|
+
`[CopilotKit Inspector] Failed to sync state for agent "${agent.agentId}":`,
|
|
624
|
+
error,
|
|
625
|
+
);
|
|
626
|
+
}
|
|
602
627
|
}
|
|
603
628
|
|
|
604
629
|
private updateContextOptions(agentIds: Set<string>): void {
|