@copilotkit/web-inspector 1.61.0 → 1.61.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 +23 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +5 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +23 -5
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +26 -7
- package/dist/index.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/web-inspector.spec.ts +184 -0
- package/src/index.ts +38 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@copilotkit/web-inspector",
|
|
3
|
-
"version": "1.61.
|
|
3
|
+
"version": "1.61.1",
|
|
4
4
|
"description": "Lit-based web component for the CopilotKit web inspector",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"lit": "^3.2.0",
|
|
28
28
|
"lucide": "^0.525.0",
|
|
29
29
|
"marked": "^12.0.2",
|
|
30
|
-
"@copilotkit/core": "1.61.
|
|
30
|
+
"@copilotkit/core": "1.61.1"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@tailwindcss/cli": "^4.1.11",
|
|
@@ -306,6 +306,9 @@ describe("WebInspectorElement", () => {
|
|
|
306
306
|
|
|
307
307
|
type ThreadDetailsInternals = {
|
|
308
308
|
threadId: string | null;
|
|
309
|
+
runtimeUrl: string;
|
|
310
|
+
headers: Record<string, string>;
|
|
311
|
+
threadInspectionAvailable: boolean;
|
|
309
312
|
liveMessageVersion: number;
|
|
310
313
|
_conversation: Array<Record<string, unknown>>;
|
|
311
314
|
_fetchedState: Record<string, unknown> | null;
|
|
@@ -318,6 +321,8 @@ type ThreadDetailsInternals = {
|
|
|
318
321
|
_loadingState: boolean;
|
|
319
322
|
_loadingEvents: boolean;
|
|
320
323
|
_panelTplCache: Map<string, { key: readonly unknown[]; tpl: unknown }>;
|
|
324
|
+
fetchEvents: (threadId: string) => Promise<void>;
|
|
325
|
+
fetchState: (threadId: string) => Promise<void>;
|
|
321
326
|
renderConversation: () => unknown;
|
|
322
327
|
renderState: () => unknown;
|
|
323
328
|
renderEvents: () => unknown;
|
|
@@ -377,6 +382,30 @@ describe("ɵCpkThreadDetails caching", () => {
|
|
|
377
382
|
expect(internals._panelTplCache.size).toBe(0);
|
|
378
383
|
});
|
|
379
384
|
|
|
385
|
+
it("does not fetch messages, events, or state when threadInspectionAvailable is omitted", async () => {
|
|
386
|
+
const fetchSpy = vi
|
|
387
|
+
.spyOn(globalThis, "fetch")
|
|
388
|
+
.mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
|
|
389
|
+
try {
|
|
390
|
+
const { el, internals } = createThreadDetails();
|
|
391
|
+
|
|
392
|
+
internals.runtimeUrl = "http://localhost:4000";
|
|
393
|
+
internals.headers = { Authorization: "Bearer test-token" };
|
|
394
|
+
internals.threadId = "t1";
|
|
395
|
+
await el.updateComplete;
|
|
396
|
+
|
|
397
|
+
expect(internals.threadInspectionAvailable).toBe(false);
|
|
398
|
+
expect(fetchSpy).not.toHaveBeenCalled();
|
|
399
|
+
|
|
400
|
+
await internals.fetchEvents("t1");
|
|
401
|
+
await internals.fetchState("t1");
|
|
402
|
+
|
|
403
|
+
expect(fetchSpy).not.toHaveBeenCalled();
|
|
404
|
+
} finally {
|
|
405
|
+
fetchSpy.mockRestore();
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
380
409
|
it("conversation cache invalidates when _conversation is reassigned", async () => {
|
|
381
410
|
const { el, internals } = createThreadDetails();
|
|
382
411
|
await settleThread(el, internals, "t1");
|
|
@@ -575,3 +604,158 @@ describe("WebInspectorElement announcement preview dismissal", () => {
|
|
|
575
604
|
expect(a.hasUnseenAnnouncement).toBe(true);
|
|
576
605
|
});
|
|
577
606
|
});
|
|
607
|
+
|
|
608
|
+
// --- Owned thread store header forwarding (issue #5581) ---
|
|
609
|
+
//
|
|
610
|
+
// When useThreads() isn't mounted, the inspector creates its own thread store
|
|
611
|
+
// per agent (ensureOwnedThreadStore). That store's /threads requests must carry
|
|
612
|
+
// the headers configured on <CopilotKit> (e.g. X-CSRF / auth), otherwise the
|
|
613
|
+
// requests 403 in environments that enforce CSRF/auth checks.
|
|
614
|
+
|
|
615
|
+
type HeaderMockCore = {
|
|
616
|
+
agents: Record<string, AbstractAgent>;
|
|
617
|
+
context: Record<string, unknown>;
|
|
618
|
+
properties: Record<string, unknown>;
|
|
619
|
+
runtimeConnectionStatus: CopilotKitCoreRuntimeConnectionStatus;
|
|
620
|
+
runtimeUrl: string;
|
|
621
|
+
headers: Record<string, string>;
|
|
622
|
+
threadEndpoints: {
|
|
623
|
+
list: boolean;
|
|
624
|
+
inspect: boolean;
|
|
625
|
+
mutations: boolean;
|
|
626
|
+
realtimeMetadata: boolean;
|
|
627
|
+
};
|
|
628
|
+
subscribe: (subscriber: CopilotKitCoreSubscriber) => {
|
|
629
|
+
unsubscribe: () => void;
|
|
630
|
+
};
|
|
631
|
+
getThreadStores: () => Record<string, never>;
|
|
632
|
+
getThreadStore: (agentId: string) => undefined;
|
|
633
|
+
registerThreadStore: (agentId: string, store: unknown) => void;
|
|
634
|
+
unregisterThreadStore: (agentId: string) => void;
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
function createHeaderMockCore(
|
|
638
|
+
agents: Record<string, AbstractAgent>,
|
|
639
|
+
headers: Record<string, string>,
|
|
640
|
+
) {
|
|
641
|
+
const subscribers = new Set<CopilotKitCoreSubscriber>();
|
|
642
|
+
const core: HeaderMockCore = {
|
|
643
|
+
agents,
|
|
644
|
+
context: {},
|
|
645
|
+
properties: {},
|
|
646
|
+
runtimeConnectionStatus: CopilotKitCoreRuntimeConnectionStatus.Connected,
|
|
647
|
+
runtimeUrl: "http://localhost/api",
|
|
648
|
+
headers,
|
|
649
|
+
threadEndpoints: {
|
|
650
|
+
list: true,
|
|
651
|
+
inspect: true,
|
|
652
|
+
mutations: true,
|
|
653
|
+
realtimeMetadata: true,
|
|
654
|
+
},
|
|
655
|
+
subscribe(subscriber: CopilotKitCoreSubscriber) {
|
|
656
|
+
subscribers.add(subscriber);
|
|
657
|
+
return { unsubscribe: () => subscribers.delete(subscriber) };
|
|
658
|
+
},
|
|
659
|
+
getThreadStores() {
|
|
660
|
+
return {};
|
|
661
|
+
},
|
|
662
|
+
getThreadStore() {
|
|
663
|
+
return undefined;
|
|
664
|
+
},
|
|
665
|
+
registerThreadStore() {},
|
|
666
|
+
unregisterThreadStore() {},
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
const asCore = () => core as unknown as CopilotKitCore;
|
|
670
|
+
return {
|
|
671
|
+
core,
|
|
672
|
+
emitAgentsChanged() {
|
|
673
|
+
subscribers.forEach((s) =>
|
|
674
|
+
s.onAgentsChanged?.({ copilotkit: asCore(), agents: core.agents }),
|
|
675
|
+
);
|
|
676
|
+
},
|
|
677
|
+
emitHeadersChanged(nextHeaders: Record<string, string>) {
|
|
678
|
+
core.headers = nextHeaders;
|
|
679
|
+
subscribers.forEach((s) =>
|
|
680
|
+
s.onHeadersChanged?.({ copilotkit: asCore(), headers: nextHeaders }),
|
|
681
|
+
);
|
|
682
|
+
},
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const headersOf = (call: unknown[]) =>
|
|
687
|
+
(call[1] as { headers?: Record<string, string> } | undefined)?.headers ?? {};
|
|
688
|
+
|
|
689
|
+
describe("WebInspectorElement owned thread store headers (#5581)", () => {
|
|
690
|
+
let fetchMock: ReturnType<typeof vi.fn>;
|
|
691
|
+
|
|
692
|
+
const threadListCalls = () =>
|
|
693
|
+
fetchMock.mock.calls.filter((call) =>
|
|
694
|
+
String(call[0]).includes("/threads?"),
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
beforeEach(() => {
|
|
698
|
+
document.body.innerHTML = "";
|
|
699
|
+
fetchMock = vi.fn(() =>
|
|
700
|
+
Promise.resolve({
|
|
701
|
+
ok: true,
|
|
702
|
+
status: 200,
|
|
703
|
+
json: () => Promise.resolve({ threads: [] }),
|
|
704
|
+
}),
|
|
705
|
+
);
|
|
706
|
+
// The owned store captures globalThis.fetch when it's created, so stub
|
|
707
|
+
// before the inspector attaches to the core.
|
|
708
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
afterEach(() => {
|
|
712
|
+
vi.unstubAllGlobals();
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it("forwards core headers on the owned store's /threads request", async () => {
|
|
716
|
+
const { agent } = createMockAgent("alpha");
|
|
717
|
+
const harness = createHeaderMockCore(
|
|
718
|
+
{ alpha: agent },
|
|
719
|
+
{ "X-CSRF": "1", Authorization: "Bearer abc" },
|
|
720
|
+
);
|
|
721
|
+
|
|
722
|
+
const inspector = new WebInspectorElement();
|
|
723
|
+
document.body.appendChild(inspector);
|
|
724
|
+
inspector.core = harness.core as unknown as WebInspectorElement["core"];
|
|
725
|
+
harness.emitAgentsChanged();
|
|
726
|
+
|
|
727
|
+
await vi.waitFor(() => {
|
|
728
|
+
expect(threadListCalls().length).toBeGreaterThan(0);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
expect(headersOf(threadListCalls()[0]!)).toMatchObject({
|
|
732
|
+
"X-CSRF": "1",
|
|
733
|
+
Authorization: "Bearer abc",
|
|
734
|
+
});
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
it("re-applies headers on the owned store when core headers change", async () => {
|
|
738
|
+
const { agent } = createMockAgent("alpha");
|
|
739
|
+
const harness = createHeaderMockCore({ alpha: agent }, { "X-CSRF": "1" });
|
|
740
|
+
|
|
741
|
+
const inspector = new WebInspectorElement();
|
|
742
|
+
document.body.appendChild(inspector);
|
|
743
|
+
inspector.core = harness.core as unknown as WebInspectorElement["core"];
|
|
744
|
+
harness.emitAgentsChanged();
|
|
745
|
+
|
|
746
|
+
await vi.waitFor(() => {
|
|
747
|
+
expect(threadListCalls().length).toBeGreaterThan(0);
|
|
748
|
+
});
|
|
749
|
+
const callsBefore = threadListCalls().length;
|
|
750
|
+
|
|
751
|
+
harness.emitHeadersChanged({ "X-CSRF": "2" });
|
|
752
|
+
|
|
753
|
+
await vi.waitFor(() => {
|
|
754
|
+
expect(threadListCalls().length).toBeGreaterThan(callsBefore);
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
expect(headersOf(threadListCalls().at(-1)!)).toMatchObject({
|
|
758
|
+
"X-CSRF": "2",
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -674,6 +674,7 @@ export class ɵCpkThreadDetails extends LitElement {
|
|
|
674
674
|
thread: { attribute: false },
|
|
675
675
|
runtimeUrl: { attribute: false },
|
|
676
676
|
headers: { attribute: false },
|
|
677
|
+
threadInspectionAvailable: { attribute: false },
|
|
677
678
|
agentStateInput: { attribute: false },
|
|
678
679
|
agentEventsInput: { attribute: false },
|
|
679
680
|
liveMessageVersion: { attribute: false },
|
|
@@ -701,6 +702,7 @@ export class ɵCpkThreadDetails extends LitElement {
|
|
|
701
702
|
thread: ɵThread | null = null;
|
|
702
703
|
runtimeUrl = "";
|
|
703
704
|
headers: Record<string, string> = {};
|
|
705
|
+
threadInspectionAvailable = false;
|
|
704
706
|
agentStateInput: Record<string, unknown> | null = null;
|
|
705
707
|
agentEventsInput: ApiAgentEvent[] = [];
|
|
706
708
|
/**
|
|
@@ -1456,7 +1458,7 @@ export class ɵCpkThreadDetails extends LitElement {
|
|
|
1456
1458
|
threadId: string,
|
|
1457
1459
|
silent: boolean = false,
|
|
1458
1460
|
): Promise<void> {
|
|
1459
|
-
if (!this.runtimeUrl) {
|
|
1461
|
+
if (!this.runtimeUrl || !this.threadInspectionAvailable) {
|
|
1460
1462
|
if (!silent) this._conversation = [];
|
|
1461
1463
|
return;
|
|
1462
1464
|
}
|
|
@@ -1494,7 +1496,7 @@ export class ɵCpkThreadDetails extends LitElement {
|
|
|
1494
1496
|
|
|
1495
1497
|
private async fetchEvents(threadId: string): Promise<void> {
|
|
1496
1498
|
this._eventsNotAvailable = false;
|
|
1497
|
-
if (!this.runtimeUrl) {
|
|
1499
|
+
if (!this.runtimeUrl || !this.threadInspectionAvailable) {
|
|
1498
1500
|
this._fetchedEvents = null;
|
|
1499
1501
|
return;
|
|
1500
1502
|
}
|
|
@@ -1541,7 +1543,7 @@ export class ɵCpkThreadDetails extends LitElement {
|
|
|
1541
1543
|
|
|
1542
1544
|
private async fetchState(threadId: string): Promise<void> {
|
|
1543
1545
|
this._stateNotAvailable = false;
|
|
1544
|
-
if (!this.runtimeUrl) {
|
|
1546
|
+
if (!this.runtimeUrl || !this.threadInspectionAvailable) {
|
|
1545
1547
|
this._fetchedState = null;
|
|
1546
1548
|
return;
|
|
1547
1549
|
}
|
|
@@ -2585,12 +2587,14 @@ export class WebInspectorElement extends LitElement {
|
|
|
2585
2587
|
if (this.core?.getThreadStore(agentId)) return;
|
|
2586
2588
|
const core = this.core;
|
|
2587
2589
|
if (!core?.runtimeUrl) return;
|
|
2590
|
+
if (core.threadEndpoints?.list === false) return;
|
|
2588
2591
|
|
|
2589
2592
|
const store = ɵcreateThreadStore({ fetch: globalThis.fetch });
|
|
2590
2593
|
store.start();
|
|
2591
2594
|
store.setContext({
|
|
2592
2595
|
runtimeUrl: core.runtimeUrl,
|
|
2593
|
-
headers: {},
|
|
2596
|
+
headers: { ...core.headers },
|
|
2597
|
+
wsUrl: core.intelligence?.wsUrl,
|
|
2594
2598
|
agentId,
|
|
2595
2599
|
});
|
|
2596
2600
|
this._ownedThreadStores.set(agentId, store);
|
|
@@ -2609,6 +2613,24 @@ export class WebInspectorElement extends LitElement {
|
|
|
2609
2613
|
store.refresh();
|
|
2610
2614
|
}
|
|
2611
2615
|
|
|
2616
|
+
// Keep inspector-owned thread stores in sync when the host updates headers
|
|
2617
|
+
// at runtime (e.g. a refreshed auth/CSRF token via core.setHeaders). Mirrors
|
|
2618
|
+
// useThreads(), which re-dispatches the context whenever core.headers change,
|
|
2619
|
+
// so the owned stores' /threads requests stay authorized.
|
|
2620
|
+
private updateOwnedThreadStoreHeaders(
|
|
2621
|
+
headers: Readonly<Record<string, string>>,
|
|
2622
|
+
): void {
|
|
2623
|
+
const core = this.core;
|
|
2624
|
+
if (!core?.runtimeUrl) return;
|
|
2625
|
+
for (const [agentId, store] of this._ownedThreadStores) {
|
|
2626
|
+
store.setContext({
|
|
2627
|
+
runtimeUrl: core.runtimeUrl,
|
|
2628
|
+
headers: { ...headers },
|
|
2629
|
+
agentId,
|
|
2630
|
+
});
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2612
2634
|
private removeOwnedThreadStore(agentId: string): void {
|
|
2613
2635
|
const store = this._ownedThreadStores.get(agentId);
|
|
2614
2636
|
if (!store) return;
|
|
@@ -2639,8 +2661,12 @@ export class WebInspectorElement extends LitElement {
|
|
|
2639
2661
|
maybeShowDisclosure();
|
|
2640
2662
|
}
|
|
2641
2663
|
this.flushPendingBannerViewed();
|
|
2642
|
-
|
|
2643
|
-
this.
|
|
2664
|
+
if (core.threadEndpoints?.list !== false) {
|
|
2665
|
+
for (const agentId of this._ownedThreadStores.keys()) {
|
|
2666
|
+
this.refreshOwnedThreadStore(agentId);
|
|
2667
|
+
}
|
|
2668
|
+
} else {
|
|
2669
|
+
this.teardownOwnedThreadStores();
|
|
2644
2670
|
}
|
|
2645
2671
|
} else {
|
|
2646
2672
|
// Clear stale thread data immediately when the server goes away
|
|
@@ -2653,6 +2679,9 @@ export class WebInspectorElement extends LitElement {
|
|
|
2653
2679
|
this.coreProperties = properties;
|
|
2654
2680
|
this.requestUpdate();
|
|
2655
2681
|
},
|
|
2682
|
+
onHeadersChanged: ({ headers }) => {
|
|
2683
|
+
this.updateOwnedThreadStoreHeaders(headers);
|
|
2684
|
+
},
|
|
2656
2685
|
onError: ({ code, error }) => {
|
|
2657
2686
|
this.lastCoreError = { code, message: error.message };
|
|
2658
2687
|
this.requestUpdate();
|
|
@@ -5855,6 +5884,9 @@ ${argsString}</pre
|
|
|
5855
5884
|
.thread=${selectedThread}
|
|
5856
5885
|
.runtimeUrl=${this._core?.runtimeUrl ?? ""}
|
|
5857
5886
|
.headers=${this._core?.headers ?? {}}
|
|
5887
|
+
.threadInspectionAvailable=${
|
|
5888
|
+
this._core?.threadEndpoints?.inspect !== false
|
|
5889
|
+
}
|
|
5858
5890
|
.liveMessageVersion=${
|
|
5859
5891
|
this.selectedThreadId
|
|
5860
5892
|
? (this.liveMessageVersion.get(this.selectedThreadId) ??
|