@copilotkit/web-inspector 1.56.2 → 1.56.4
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 +19 -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 +19 -8
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +19 -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 +33 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@copilotkit/web-inspector",
|
|
3
|
-
"version": "1.56.
|
|
3
|
+
"version": "1.56.4",
|
|
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.56.
|
|
26
|
+
"@copilotkit/core": "1.56.4"
|
|
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
|
@@ -496,6 +496,9 @@ export class WebInspectorElement extends LitElement {
|
|
|
496
496
|
onMessagesChanged: () => {
|
|
497
497
|
this.syncAgentMessages(agent);
|
|
498
498
|
},
|
|
499
|
+
onStateChanged: () => {
|
|
500
|
+
this.syncAgentState(agent);
|
|
501
|
+
},
|
|
499
502
|
onRawEvent: ({ event }) => {
|
|
500
503
|
this.recordAgentEvent(agentId, "RAW_EVENT", event);
|
|
501
504
|
},
|
|
@@ -581,16 +584,23 @@ export class WebInspectorElement extends LitElement {
|
|
|
581
584
|
return;
|
|
582
585
|
}
|
|
583
586
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
+
}
|
|
592
596
|
|
|
593
|
-
|
|
597
|
+
this.requestUpdate();
|
|
598
|
+
} catch (error) {
|
|
599
|
+
console.error(
|
|
600
|
+
`[CopilotKit Inspector] Failed to sync messages for agent "${agent.agentId}":`,
|
|
601
|
+
error,
|
|
602
|
+
);
|
|
603
|
+
}
|
|
594
604
|
}
|
|
595
605
|
|
|
596
606
|
private syncAgentState(agent: AbstractAgent): void {
|
|
@@ -598,15 +608,22 @@ export class WebInspectorElement extends LitElement {
|
|
|
598
608
|
return;
|
|
599
609
|
}
|
|
600
610
|
|
|
601
|
-
|
|
611
|
+
try {
|
|
612
|
+
const state = (agent as { state?: unknown }).state;
|
|
602
613
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
614
|
+
if (state === undefined || state === null) {
|
|
615
|
+
this.agentStates.delete(agent.agentId);
|
|
616
|
+
} else {
|
|
617
|
+
this.agentStates.set(agent.agentId, this.sanitizeForLogging(state));
|
|
618
|
+
}
|
|
608
619
|
|
|
609
|
-
|
|
620
|
+
this.requestUpdate();
|
|
621
|
+
} catch (error) {
|
|
622
|
+
console.error(
|
|
623
|
+
`[CopilotKit Inspector] Failed to sync state for agent "${agent.agentId}":`,
|
|
624
|
+
error,
|
|
625
|
+
);
|
|
626
|
+
}
|
|
610
627
|
}
|
|
611
628
|
|
|
612
629
|
private updateContextOptions(agentIds: Set<string>): void {
|