@copilotkitnext/angular 0.0.2 → 0.0.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/README.md +3 -3
- package/dist/README.md +3 -3
- package/dist/components/chat/copilot-chat-assistant-message.component.d.ts +10 -10
- package/dist/components/chat/copilot-chat-message-view.component.d.ts +42 -42
- package/dist/components/chat/copilot-chat-view.component.d.ts +14 -14
- package/dist/esm2022/components/chat/copilot-chat-assistant-message-buttons.component.mjs +384 -0
- package/dist/esm2022/components/chat/copilot-chat-assistant-message-renderer.component.mjs +286 -0
- package/dist/esm2022/components/chat/copilot-chat-assistant-message-toolbar.component.mjs +27 -0
- package/dist/esm2022/components/chat/copilot-chat-assistant-message.component.mjs +433 -0
- package/dist/esm2022/components/chat/copilot-chat-assistant-message.types.mjs +2 -0
- package/dist/esm2022/components/chat/copilot-chat-audio-recorder.component.mjs +202 -0
- package/dist/esm2022/components/chat/copilot-chat-buttons.component.mjs +321 -0
- package/dist/esm2022/components/chat/copilot-chat-input-defaults.mjs +38 -0
- package/dist/esm2022/components/chat/copilot-chat-input.component.mjs +666 -0
- package/dist/esm2022/components/chat/copilot-chat-input.types.mjs +10 -0
- package/dist/esm2022/components/chat/copilot-chat-message-view-cursor.component.mjs +45 -0
- package/dist/esm2022/components/chat/copilot-chat-message-view.component.mjs +296 -0
- package/dist/esm2022/components/chat/copilot-chat-message-view.types.mjs +2 -0
- package/dist/esm2022/components/chat/copilot-chat-textarea.component.mjs +188 -0
- package/dist/esm2022/components/chat/copilot-chat-tool-calls-view.component.mjs +216 -0
- package/dist/esm2022/components/chat/copilot-chat-toolbar.component.mjs +25 -0
- package/dist/esm2022/components/chat/copilot-chat-tools-menu.component.mjs +199 -0
- package/dist/esm2022/components/chat/copilot-chat-user-message-branch-navigation.component.mjs +137 -0
- package/dist/esm2022/components/chat/copilot-chat-user-message-buttons.component.mjs +207 -0
- package/dist/esm2022/components/chat/copilot-chat-user-message-renderer.component.mjs +35 -0
- package/dist/esm2022/components/chat/copilot-chat-user-message-toolbar.component.mjs +34 -0
- package/dist/esm2022/components/chat/copilot-chat-user-message.component.mjs +341 -0
- package/dist/esm2022/components/chat/copilot-chat-user-message.types.mjs +2 -0
- package/dist/esm2022/components/chat/copilot-chat-view-disclaimer.component.mjs +52 -0
- package/dist/esm2022/components/chat/copilot-chat-view-feather.component.mjs +55 -0
- package/dist/esm2022/components/chat/copilot-chat-view-handlers.service.mjs +19 -0
- package/dist/esm2022/components/chat/copilot-chat-view-input-container.component.mjs +110 -0
- package/dist/esm2022/components/chat/copilot-chat-view-scroll-to-bottom-button.component.mjs +93 -0
- package/dist/esm2022/components/chat/copilot-chat-view-scroll-view.component.mjs +443 -0
- package/dist/esm2022/components/chat/copilot-chat-view.component.mjs +479 -0
- package/dist/esm2022/components/chat/copilot-chat-view.types.mjs +2 -0
- package/dist/esm2022/components/chat/copilot-chat.component.mjs +214 -0
- package/dist/esm2022/components/copilotkit-tool-render.component.mjs +153 -0
- package/dist/esm2022/copilotkitnext-angular.mjs +5 -0
- package/dist/esm2022/core/chat-configuration/chat-configuration.providers.mjs +65 -0
- package/dist/esm2022/core/chat-configuration/chat-configuration.service.mjs +145 -0
- package/dist/esm2022/core/chat-configuration/chat-configuration.types.mjs +26 -0
- package/dist/esm2022/core/copilotkit.providers.mjs +34 -0
- package/dist/esm2022/core/copilotkit.service.mjs +430 -0
- package/dist/esm2022/core/copilotkit.types.mjs +12 -0
- package/dist/esm2022/directives/copilotkit-agent-context.directive.mjs +130 -0
- package/dist/esm2022/directives/copilotkit-agent.directive.mjs +217 -0
- package/dist/esm2022/directives/copilotkit-chat-config.directive.mjs +218 -0
- package/dist/esm2022/directives/copilotkit-config.directive.mjs +94 -0
- package/dist/esm2022/directives/copilotkit-frontend-tool.directive.mjs +130 -0
- package/dist/esm2022/directives/copilotkit-human-in-the-loop.directive.mjs +266 -0
- package/dist/esm2022/directives/stick-to-bottom.directive.mjs +181 -0
- package/dist/esm2022/index.mjs +70 -0
- package/dist/esm2022/lib/directives/tooltip.directive.mjs +211 -0
- package/dist/esm2022/lib/slots/copilot-slot.component.mjs +144 -0
- package/dist/esm2022/lib/slots/slot.types.mjs +6 -0
- package/dist/esm2022/lib/slots/slot.utils.mjs +222 -0
- package/dist/esm2022/lib/utils.mjs +10 -0
- package/dist/esm2022/services/resize-observer.service.mjs +152 -0
- package/dist/esm2022/services/scroll-position.service.mjs +124 -0
- package/dist/esm2022/types/frontend-tool.mjs +2 -0
- package/dist/esm2022/types/human-in-the-loop.mjs +2 -0
- package/dist/esm2022/utils/agent-context.utils.mjs +114 -0
- package/dist/esm2022/utils/agent.utils.mjs +204 -0
- package/dist/esm2022/utils/chat-config.utils.mjs +186 -0
- package/dist/esm2022/utils/copilotkit.utils.mjs +20 -0
- package/dist/esm2022/utils/frontend-tool.utils.mjs +228 -0
- package/dist/esm2022/utils/human-in-the-loop.utils.mjs +296 -0
- package/dist/fesm2022/copilotkitnext-angular.mjs +163 -164
- package/dist/fesm2022/copilotkitnext-angular.mjs.map +1 -1
- package/package.json +21 -18
- package/vitest.config.mts +32 -21
- package/.turbo/turbo-build.log +0 -38
- package/.turbo/turbo-check-types.log +0 -0
- package/.turbo/turbo-test.log +0 -71
- package/ng-package.json +0 -19
- package/src/components/chat/__tests__/copilot-chat-assistant-message.component.spec.ts +0 -282
- package/src/components/chat/__tests__/copilot-chat-input.component.spec.ts +0 -419
- package/src/components/chat/__tests__/copilot-chat-message-view.component.spec.ts +0 -372
- package/src/components/chat/__tests__/copilot-chat-user-message.component.spec.ts +0 -249
- package/src/components/chat/copilot-chat-assistant-message-buttons.component.ts +0 -292
- package/src/components/chat/copilot-chat-assistant-message-renderer.component.ts +0 -472
- package/src/components/chat/copilot-chat-assistant-message-toolbar.component.ts +0 -29
- package/src/components/chat/copilot-chat-assistant-message.component.ts +0 -463
- package/src/components/chat/copilot-chat-assistant-message.types.ts +0 -50
- package/src/components/chat/copilot-chat-audio-recorder.component.ts +0 -241
- package/src/components/chat/copilot-chat-buttons.component.ts +0 -308
- package/src/components/chat/copilot-chat-buttons.component.ts.bak +0 -471
- package/src/components/chat/copilot-chat-input-defaults.ts +0 -47
- package/src/components/chat/copilot-chat-input.component.ts +0 -512
- package/src/components/chat/copilot-chat-input.types.ts +0 -148
- package/src/components/chat/copilot-chat-message-view-cursor.component.ts +0 -51
- package/src/components/chat/copilot-chat-message-view.component.ts +0 -233
- package/src/components/chat/copilot-chat-message-view.types.ts +0 -39
- package/src/components/chat/copilot-chat-textarea.component.ts +0 -220
- package/src/components/chat/copilot-chat-tool-calls-view.component.ts +0 -261
- package/src/components/chat/copilot-chat-toolbar.component.ts +0 -35
- package/src/components/chat/copilot-chat-tools-menu.component.ts +0 -185
- package/src/components/chat/copilot-chat-user-message-branch-navigation.component.ts +0 -121
- package/src/components/chat/copilot-chat-user-message-buttons.component.ts +0 -170
- package/src/components/chat/copilot-chat-user-message-renderer.component.ts +0 -37
- package/src/components/chat/copilot-chat-user-message-toolbar.component.ts +0 -37
- package/src/components/chat/copilot-chat-user-message.component.ts +0 -247
- package/src/components/chat/copilot-chat-user-message.types.ts +0 -42
- package/src/components/chat/copilot-chat-view-disclaimer.component.ts +0 -51
- package/src/components/chat/copilot-chat-view-feather.component.ts +0 -47
- package/src/components/chat/copilot-chat-view-handlers.service.ts +0 -14
- package/src/components/chat/copilot-chat-view-input-container.component.ts +0 -87
- package/src/components/chat/copilot-chat-view-scroll-to-bottom-button.component.ts +0 -79
- package/src/components/chat/copilot-chat-view-scroll-view.component.ts +0 -322
- package/src/components/chat/copilot-chat-view.component.ts +0 -420
- package/src/components/chat/copilot-chat-view.types.ts +0 -52
- package/src/components/chat/copilot-chat.component.ts +0 -232
- package/src/components/copilotkit-tool-render.component.ts +0 -169
- package/src/core/__tests__/copilotkit.service.spec.ts +0 -1051
- package/src/core/__tests__/copilotkit.service.wildcard.spec.ts +0 -316
- package/src/core/chat-configuration/__tests__/chat-configuration.service.spec.ts +0 -287
- package/src/core/chat-configuration/chat-configuration.providers.ts +0 -71
- package/src/core/chat-configuration/chat-configuration.service.ts +0 -162
- package/src/core/chat-configuration/chat-configuration.types.ts +0 -57
- package/src/core/copilotkit.providers.ts +0 -59
- package/src/core/copilotkit.service.ts +0 -542
- package/src/core/copilotkit.types.ts +0 -132
- package/src/directives/__tests__/copilotkit-agent-context.directive.spec.ts +0 -384
- package/src/directives/__tests__/copilotkit-agent.directive.spec.ts +0 -253
- package/src/directives/__tests__/copilotkit-chat-config.directive.spec.ts +0 -385
- package/src/directives/__tests__/copilotkit-config.directive.spec.ts +0 -69
- package/src/directives/__tests__/copilotkit-frontend-tool-simple.directive.spec.ts +0 -60
- package/src/directives/__tests__/copilotkit-frontend-tool.directive.spec.ts +0 -108
- package/src/directives/__tests__/copilotkit-human-in-the-loop.directive.spec.ts +0 -452
- package/src/directives/copilotkit-agent-context.directive.ts +0 -138
- package/src/directives/copilotkit-agent.directive.ts +0 -225
- package/src/directives/copilotkit-chat-config.directive.ts +0 -241
- package/src/directives/copilotkit-config.directive.ts +0 -81
- package/src/directives/copilotkit-frontend-tool.directive.ts +0 -145
- package/src/directives/copilotkit-human-in-the-loop.directive.ts +0 -281
- package/src/directives/stick-to-bottom.directive.ts +0 -204
- package/src/index.ts +0 -105
- package/src/lib/directives/tooltip.directive.ts +0 -292
- package/src/lib/slots/__tests__/slot.utils.spec.ts +0 -377
- package/src/lib/slots/copilot-slot.component.ts +0 -135
- package/src/lib/slots/index.ts +0 -3
- package/src/lib/slots/slot.types.ts +0 -64
- package/src/lib/slots/slot.utils.ts +0 -289
- package/src/lib/utils.ts +0 -10
- package/src/public-api.ts +0 -1
- package/src/services/resize-observer.service.ts +0 -181
- package/src/services/scroll-position.service.ts +0 -169
- package/src/styles/globals.css +0 -266
- package/src/styles/index.css +0 -3
- package/src/test-setup.ts +0 -15
- package/src/testing/index.ts +0 -3
- package/src/testing/testing.utils.ts +0 -248
- package/src/types/frontend-tool.ts +0 -44
- package/src/types/human-in-the-loop.ts +0 -52
- package/src/utils/__tests__/agent.utils.spec.ts +0 -234
- package/src/utils/__tests__/chat-config.utils.spec.ts +0 -306
- package/src/utils/__tests__/frontend-tool-inject.spec.ts +0 -350
- package/src/utils/__tests__/frontend-tool-integration.spec.ts +0 -199
- package/src/utils/__tests__/frontend-tool.utils.spec.ts +0 -272
- package/src/utils/__tests__/human-in-the-loop.utils.spec.ts +0 -365
- package/src/utils/agent-context.utils.ts +0 -133
- package/src/utils/agent.utils.ts +0 -239
- package/src/utils/chat-config.utils.ts +0 -221
- package/src/utils/copilotkit.utils.ts +0 -20
- package/src/utils/frontend-tool.utils.ts +0 -266
- package/src/utils/human-in-the-loop.utils.ts +0 -359
- package/tsconfig.spec.json +0 -12
|
@@ -1,1051 +0,0 @@
|
|
|
1
|
-
import { TestBed } from "@angular/core/testing";
|
|
2
|
-
import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
|
|
3
|
-
import { CopilotKitService } from "../copilotkit.service";
|
|
4
|
-
import { CopilotKitCore } from "@copilotkitnext/core";
|
|
5
|
-
import {
|
|
6
|
-
effect,
|
|
7
|
-
runInInjectionContext,
|
|
8
|
-
Injector,
|
|
9
|
-
Component,
|
|
10
|
-
} from "@angular/core";
|
|
11
|
-
import {
|
|
12
|
-
createCopilotKitTestingModule,
|
|
13
|
-
MockDestroyRef,
|
|
14
|
-
} from "../../testing/testing.utils";
|
|
15
|
-
import { provideCopilotKit } from "../copilotkit.providers";
|
|
16
|
-
import { AngularFrontendTool } from "../../types/frontend-tool";
|
|
17
|
-
import { AngularHumanInTheLoop } from "../../types/human-in-the-loop";
|
|
18
|
-
import { z } from "zod";
|
|
19
|
-
|
|
20
|
-
// Mock the entire @copilotkitnext/core module to avoid any network calls
|
|
21
|
-
let mockSubscribers: Array<any> = [];
|
|
22
|
-
|
|
23
|
-
vi.mock("@copilotkitnext/core", () => {
|
|
24
|
-
// Don't import the real module at all
|
|
25
|
-
return {
|
|
26
|
-
CopilotKitCore: vi.fn().mockImplementation((config) => {
|
|
27
|
-
// Reset subscribers for each instance
|
|
28
|
-
mockSubscribers = [];
|
|
29
|
-
const instance = {
|
|
30
|
-
setRuntimeUrl: vi.fn(),
|
|
31
|
-
setHeaders: vi.fn(),
|
|
32
|
-
setProperties: vi.fn(),
|
|
33
|
-
setAgents: vi.fn(),
|
|
34
|
-
tools: config?.tools || {},
|
|
35
|
-
subscribe: vi.fn((callbacks) => {
|
|
36
|
-
mockSubscribers.push(callbacks);
|
|
37
|
-
// Return unsubscribe function
|
|
38
|
-
return () => {
|
|
39
|
-
const index = mockSubscribers.indexOf(callbacks);
|
|
40
|
-
if (index > -1) mockSubscribers.splice(index, 1);
|
|
41
|
-
};
|
|
42
|
-
}),
|
|
43
|
-
// Helper to trigger events in tests
|
|
44
|
-
_triggerRuntimeLoaded: () => {
|
|
45
|
-
mockSubscribers.forEach((sub) => sub.onRuntimeLoaded?.());
|
|
46
|
-
},
|
|
47
|
-
_triggerRuntimeError: () => {
|
|
48
|
-
mockSubscribers.forEach((sub) => sub.onRuntimeLoadError?.());
|
|
49
|
-
},
|
|
50
|
-
_getSubscriberCount: () => mockSubscribers.length,
|
|
51
|
-
isRuntimeReady: false,
|
|
52
|
-
runtimeError: null,
|
|
53
|
-
messages: [],
|
|
54
|
-
// Add any other properties that might be accessed
|
|
55
|
-
state: "idle",
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// Store the config tools for later access
|
|
59
|
-
if (config?.tools) {
|
|
60
|
-
instance.tools = config.tools;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return instance;
|
|
64
|
-
}),
|
|
65
|
-
};
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe("CopilotKitService", () => {
|
|
69
|
-
let service: CopilotKitService;
|
|
70
|
-
let mockCopilotKitCore: any;
|
|
71
|
-
let mockDestroyRef: MockDestroyRef;
|
|
72
|
-
let testBed: any;
|
|
73
|
-
|
|
74
|
-
beforeEach(() => {
|
|
75
|
-
testBed = createCopilotKitTestingModule({}, undefined, [CopilotKitService]);
|
|
76
|
-
mockDestroyRef = testBed.mockDestroyRef;
|
|
77
|
-
service = TestBed.inject(CopilotKitService);
|
|
78
|
-
mockCopilotKitCore = service.copilotkit;
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
afterEach(() => {
|
|
82
|
-
TestBed.resetTestingModule();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
afterEach(() => {
|
|
86
|
-
vi.clearAllMocks();
|
|
87
|
-
mockSubscribers = [];
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe("Singleton Behavior", () => {
|
|
91
|
-
it("should return the same service instance when injected multiple times", () => {
|
|
92
|
-
const service2 = TestBed.inject(CopilotKitService);
|
|
93
|
-
expect(service).toBe(service2);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("should use the same CopilotKitCore instance across injections", () => {
|
|
97
|
-
const service2 = TestBed.inject(CopilotKitService);
|
|
98
|
-
expect(service.copilotkit).toBe(service2.copilotkit);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("should share state between multiple service references", () => {
|
|
102
|
-
const service2 = TestBed.inject(CopilotKitService);
|
|
103
|
-
|
|
104
|
-
// Update state through first reference
|
|
105
|
-
service.setRuntimeUrl("test-url");
|
|
106
|
-
|
|
107
|
-
// Check state through second reference
|
|
108
|
-
expect(service2.runtimeUrl()).toBe("test-url");
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe("Network Mocking", () => {
|
|
113
|
-
it("should not make any network calls on initialization", () => {
|
|
114
|
-
// The mocked CopilotKitCore should not make any actual network calls
|
|
115
|
-
// If it did, the test would fail as we've completely mocked the module
|
|
116
|
-
expect(mockCopilotKitCore.setRuntimeUrl).toBeDefined();
|
|
117
|
-
|
|
118
|
-
// Verify initial state has no runtime URL to prevent auto-fetching
|
|
119
|
-
expect(mockCopilotKitCore.setRuntimeUrl).not.toHaveBeenCalledWith(
|
|
120
|
-
expect.stringContaining("http")
|
|
121
|
-
);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("should call mocked setRuntimeUrl when runtime URL is updated", async () => {
|
|
125
|
-
service.setRuntimeUrl("https://test.com");
|
|
126
|
-
|
|
127
|
-
// Give effects time to run
|
|
128
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
129
|
-
|
|
130
|
-
expect(mockCopilotKitCore.setRuntimeUrl).toHaveBeenCalledWith(
|
|
131
|
-
"https://test.com"
|
|
132
|
-
);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
describe("Reactivity - Signal Updates", () => {
|
|
137
|
-
it("should update signals when setters are called", () => {
|
|
138
|
-
service.setRuntimeUrl("test-url");
|
|
139
|
-
expect(service.runtimeUrl()).toBe("test-url");
|
|
140
|
-
|
|
141
|
-
service.setHeaders({ "X-Test": "value" });
|
|
142
|
-
expect(service.headers()).toEqual({ "X-Test": "value" });
|
|
143
|
-
|
|
144
|
-
service.setProperties({ prop: "value" });
|
|
145
|
-
expect(service.properties()).toEqual({ prop: "value" });
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it("should trigger computed signal updates when dependencies change", () => {
|
|
149
|
-
let contextValue = service.context();
|
|
150
|
-
expect(contextValue.copilotkit).toBe(mockCopilotKitCore);
|
|
151
|
-
|
|
152
|
-
// Change render tool calls
|
|
153
|
-
service.setCurrentRenderToolCalls([
|
|
154
|
-
{ name: "test", args: {} as any, render: {} as any },
|
|
155
|
-
]);
|
|
156
|
-
|
|
157
|
-
// Get new context value
|
|
158
|
-
contextValue = service.context();
|
|
159
|
-
expect(contextValue.currentRenderToolCalls).toEqual([
|
|
160
|
-
{ name: "test", args: {}, render: {} },
|
|
161
|
-
]);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it("should increment runtimeStateVersion when runtime events occur", () => {
|
|
165
|
-
const initialVersion = service.runtimeStateVersion();
|
|
166
|
-
|
|
167
|
-
// Trigger runtime loaded event
|
|
168
|
-
mockCopilotKitCore._triggerRuntimeLoaded();
|
|
169
|
-
|
|
170
|
-
expect(service.runtimeStateVersion()).toBeGreaterThan(initialVersion);
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
describe("Reactivity - Observable Updates", () => {
|
|
175
|
-
it("should emit on observables when signals change", async () => {
|
|
176
|
-
const values: string[] = [];
|
|
177
|
-
const subscription = service.runtimeUrl$.subscribe((value) => {
|
|
178
|
-
values.push(value || "undefined");
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
// Wait for initial emission
|
|
182
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
183
|
-
expect(values).toContain("undefined");
|
|
184
|
-
|
|
185
|
-
// Update the signal
|
|
186
|
-
service.setRuntimeUrl("test-url-1");
|
|
187
|
-
|
|
188
|
-
// Wait a tick for the observable to emit
|
|
189
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
190
|
-
|
|
191
|
-
expect(values).toContain("test-url-1");
|
|
192
|
-
|
|
193
|
-
subscription.unsubscribe();
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it("should emit context changes through context$", async () => {
|
|
197
|
-
const contexts: any[] = [];
|
|
198
|
-
const subscription = service.context$.subscribe((ctx) => {
|
|
199
|
-
contexts.push(ctx);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// Wait for initial emission
|
|
203
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
204
|
-
expect(contexts.length).toBeGreaterThan(0);
|
|
205
|
-
|
|
206
|
-
// Trigger a change
|
|
207
|
-
service.setCurrentRenderToolCalls([
|
|
208
|
-
{ name: "newTool", args: {} as any, render: {} as any },
|
|
209
|
-
]);
|
|
210
|
-
|
|
211
|
-
// Wait for observable emission
|
|
212
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
213
|
-
|
|
214
|
-
const lastContext = contexts[contexts.length - 1];
|
|
215
|
-
expect(lastContext.currentRenderToolCalls).toEqual([
|
|
216
|
-
{ name: "newTool", args: {}, render: {} },
|
|
217
|
-
]);
|
|
218
|
-
|
|
219
|
-
subscription.unsubscribe();
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
describe("Runtime Event Subscriptions", () => {
|
|
224
|
-
it("should subscribe to runtime events on initialization", () => {
|
|
225
|
-
// Service should have subscribed during construction
|
|
226
|
-
expect(mockCopilotKitCore.subscribe).toHaveBeenCalled();
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it("should have exactly one subscription to runtime events", () => {
|
|
230
|
-
// Check that subscribe was called exactly once
|
|
231
|
-
expect(mockCopilotKitCore.subscribe).toHaveBeenCalledTimes(1);
|
|
232
|
-
|
|
233
|
-
// Also check using the helper method
|
|
234
|
-
expect(mockCopilotKitCore._getSubscriberCount()).toBe(1);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it("should react to runtime loaded event", () => {
|
|
238
|
-
const initialVersion = service.runtimeStateVersion();
|
|
239
|
-
|
|
240
|
-
mockCopilotKitCore._triggerRuntimeLoaded();
|
|
241
|
-
|
|
242
|
-
expect(service.runtimeStateVersion()).toBe(initialVersion + 1);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it("should react to runtime error event", () => {
|
|
246
|
-
const initialVersion = service.runtimeStateVersion();
|
|
247
|
-
|
|
248
|
-
mockCopilotKitCore._triggerRuntimeError();
|
|
249
|
-
|
|
250
|
-
expect(service.runtimeStateVersion()).toBe(initialVersion + 1);
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
describe("Effects Synchronization", () => {
|
|
255
|
-
it("should sync runtime URL changes to CopilotKitCore", async () => {
|
|
256
|
-
service.setRuntimeUrl("https://api.test.com");
|
|
257
|
-
|
|
258
|
-
// Give effects time to run
|
|
259
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
260
|
-
|
|
261
|
-
expect(mockCopilotKitCore.setRuntimeUrl).toHaveBeenCalledWith(
|
|
262
|
-
"https://api.test.com"
|
|
263
|
-
);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
it("should sync all configuration changes to CopilotKitCore", async () => {
|
|
267
|
-
service.setRuntimeUrl("url");
|
|
268
|
-
service.setHeaders({ key: "value" });
|
|
269
|
-
service.setProperties({ prop: "val" });
|
|
270
|
-
service.setAgents({ agent1: {} as any });
|
|
271
|
-
|
|
272
|
-
// Give effects time to run
|
|
273
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
274
|
-
|
|
275
|
-
expect(mockCopilotKitCore.setRuntimeUrl).toHaveBeenCalledWith("url");
|
|
276
|
-
expect(mockCopilotKitCore.setHeaders).toHaveBeenCalledWith({
|
|
277
|
-
key: "value",
|
|
278
|
-
});
|
|
279
|
-
expect(mockCopilotKitCore.setProperties).toHaveBeenCalledWith({
|
|
280
|
-
prop: "val",
|
|
281
|
-
});
|
|
282
|
-
expect(mockCopilotKitCore.setAgents).toHaveBeenCalledWith({ agent1: {} });
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
describe("Component Integration Simulation", () => {
|
|
287
|
-
it("should allow components to react to runtime state changes", async () => {
|
|
288
|
-
const injector = TestBed.inject(Injector);
|
|
289
|
-
let effectRunCount = 0;
|
|
290
|
-
let lastVersion = 0;
|
|
291
|
-
|
|
292
|
-
// Simulate a component using effect to watch runtime state
|
|
293
|
-
runInInjectionContext(injector, () => {
|
|
294
|
-
effect(() => {
|
|
295
|
-
lastVersion = service.runtimeStateVersion();
|
|
296
|
-
effectRunCount++;
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
// Wait for initial effect to run
|
|
301
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
302
|
-
|
|
303
|
-
// Effect should run initially
|
|
304
|
-
expect(effectRunCount).toBeGreaterThan(0);
|
|
305
|
-
const initialVersion = lastVersion;
|
|
306
|
-
|
|
307
|
-
// Trigger runtime event
|
|
308
|
-
mockCopilotKitCore._triggerRuntimeLoaded();
|
|
309
|
-
|
|
310
|
-
// Wait for effect to run
|
|
311
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
312
|
-
|
|
313
|
-
// Effect should have run again with new version
|
|
314
|
-
expect(lastVersion).toBeGreaterThan(initialVersion);
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it("should allow multiple components to track state independently", async () => {
|
|
318
|
-
const injector = TestBed.inject(Injector);
|
|
319
|
-
const component1Values: string[] = [];
|
|
320
|
-
const component2Values: string[] = [];
|
|
321
|
-
|
|
322
|
-
// Simulate two components watching the same state
|
|
323
|
-
runInInjectionContext(injector, () => {
|
|
324
|
-
effect(() => {
|
|
325
|
-
component1Values.push(service.runtimeUrl() || "none");
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
effect(() => {
|
|
329
|
-
component2Values.push(service.runtimeUrl() || "none");
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
// Wait for initial effects to run
|
|
334
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
335
|
-
|
|
336
|
-
// Both should have initial value
|
|
337
|
-
expect(component1Values).toContain("none");
|
|
338
|
-
expect(component2Values).toContain("none");
|
|
339
|
-
|
|
340
|
-
// Update state
|
|
341
|
-
service.setRuntimeUrl("shared-url");
|
|
342
|
-
|
|
343
|
-
// Wait for effects to run
|
|
344
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
345
|
-
|
|
346
|
-
// Both should receive update
|
|
347
|
-
expect(component1Values).toContain("shared-url");
|
|
348
|
-
expect(component2Values).toContain("shared-url");
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
describe("Memory Management", () => {
|
|
353
|
-
it.skip("should properly clean up subscriptions on destroy", () => {
|
|
354
|
-
// Skipped: This test relies on complex mock interactions that don't
|
|
355
|
-
// accurately reflect the real Angular DI behavior. The actual service
|
|
356
|
-
// correctly cleans up via DestroyRef in production.
|
|
357
|
-
|
|
358
|
-
// Initially should have one subscriber
|
|
359
|
-
expect(mockCopilotKitCore._getSubscriberCount()).toBe(1);
|
|
360
|
-
|
|
361
|
-
// Trigger destroy
|
|
362
|
-
mockDestroyRef.destroy();
|
|
363
|
-
|
|
364
|
-
// Should have no subscribers
|
|
365
|
-
expect(mockCopilotKitCore._getSubscriberCount()).toBe(0);
|
|
366
|
-
});
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
describe("Edge Cases and Error Handling", () => {
|
|
370
|
-
it("should handle rapid successive runtime state changes", () => {
|
|
371
|
-
const initialVersion = service.runtimeStateVersion();
|
|
372
|
-
|
|
373
|
-
// Trigger multiple events rapidly
|
|
374
|
-
for (let i = 0; i < 10; i++) {
|
|
375
|
-
mockCopilotKitCore._triggerRuntimeLoaded();
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Should have incremented correctly
|
|
379
|
-
expect(service.runtimeStateVersion()).toBe(initialVersion + 10);
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
it("should handle undefined runtime URL gracefully", async () => {
|
|
383
|
-
service.setRuntimeUrl(undefined);
|
|
384
|
-
|
|
385
|
-
// Give effects time to run
|
|
386
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
387
|
-
|
|
388
|
-
expect(mockCopilotKitCore.setRuntimeUrl).toHaveBeenCalledWith(undefined);
|
|
389
|
-
expect(service.runtimeUrl()).toBeUndefined();
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
it("should handle empty objects gracefully", async () => {
|
|
393
|
-
service.setHeaders({});
|
|
394
|
-
service.setProperties({});
|
|
395
|
-
service.setAgents({});
|
|
396
|
-
|
|
397
|
-
// Give effects time to run
|
|
398
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
399
|
-
|
|
400
|
-
expect(mockCopilotKitCore.setHeaders).toHaveBeenCalledWith({});
|
|
401
|
-
expect(mockCopilotKitCore.setProperties).toHaveBeenCalledWith({});
|
|
402
|
-
expect(mockCopilotKitCore.setAgents).toHaveBeenCalledWith({});
|
|
403
|
-
});
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
describe("Observable Behavior", () => {
|
|
407
|
-
it("should provide working observables for all signals", () => {
|
|
408
|
-
expect(service.renderToolCalls$).toBeDefined();
|
|
409
|
-
expect(service.currentRenderToolCalls$).toBeDefined();
|
|
410
|
-
expect(service.runtimeUrl$).toBeDefined();
|
|
411
|
-
expect(service.headers$).toBeDefined();
|
|
412
|
-
expect(service.properties$).toBeDefined();
|
|
413
|
-
expect(service.agents$).toBeDefined();
|
|
414
|
-
expect(service.context$).toBeDefined();
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
it("should allow multiple observable subscriptions", async () => {
|
|
418
|
-
const sub1Values: any[] = [];
|
|
419
|
-
const sub2Values: any[] = [];
|
|
420
|
-
|
|
421
|
-
const sub1 = service.runtimeUrl$.subscribe((v) => sub1Values.push(v));
|
|
422
|
-
const sub2 = service.runtimeUrl$.subscribe((v) => sub2Values.push(v));
|
|
423
|
-
|
|
424
|
-
// Wait for initial emissions
|
|
425
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
426
|
-
|
|
427
|
-
// Both should get initial value
|
|
428
|
-
expect(sub1Values.length).toBeGreaterThan(0);
|
|
429
|
-
expect(sub2Values.length).toBeGreaterThan(0);
|
|
430
|
-
|
|
431
|
-
sub1.unsubscribe();
|
|
432
|
-
sub2.unsubscribe();
|
|
433
|
-
});
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
describe("State Consistency", () => {
|
|
437
|
-
it("should maintain consistent state across all access patterns", async () => {
|
|
438
|
-
const testUrl = "consistency-test-url";
|
|
439
|
-
service.setRuntimeUrl(testUrl);
|
|
440
|
-
|
|
441
|
-
// Check signal
|
|
442
|
-
expect(service.runtimeUrl()).toBe(testUrl);
|
|
443
|
-
|
|
444
|
-
// Give effects time to run
|
|
445
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
446
|
-
|
|
447
|
-
// Check that effect synced to core
|
|
448
|
-
expect(mockCopilotKitCore.setRuntimeUrl).toHaveBeenCalledWith(testUrl);
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
it("should not lose state during rapid updates", async () => {
|
|
452
|
-
const urls = ["url1", "url2", "url3", "url4", "url5"];
|
|
453
|
-
|
|
454
|
-
urls.forEach((url) => {
|
|
455
|
-
service.setRuntimeUrl(url);
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
// Final state should be the last URL
|
|
459
|
-
expect(service.runtimeUrl()).toBe("url5");
|
|
460
|
-
|
|
461
|
-
// Give effects time to run
|
|
462
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
463
|
-
|
|
464
|
-
// Core should have been called with the last URL
|
|
465
|
-
expect(mockCopilotKitCore.setRuntimeUrl).toHaveBeenLastCalledWith("url5");
|
|
466
|
-
});
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
describe("Integration with Angular Change Detection", () => {
|
|
470
|
-
it("should trigger change detection through signal updates", async () => {
|
|
471
|
-
const injector = TestBed.inject(Injector);
|
|
472
|
-
let changeDetectionRuns = 0;
|
|
473
|
-
|
|
474
|
-
runInInjectionContext(injector, () => {
|
|
475
|
-
effect(() => {
|
|
476
|
-
// This effect simulates Angular's change detection
|
|
477
|
-
const _ = service.runtimeStateVersion();
|
|
478
|
-
changeDetectionRuns++;
|
|
479
|
-
});
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
// Wait for initial effect to run
|
|
483
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
484
|
-
|
|
485
|
-
const initialRuns = changeDetectionRuns;
|
|
486
|
-
|
|
487
|
-
// Trigger runtime event
|
|
488
|
-
mockCopilotKitCore._triggerRuntimeLoaded();
|
|
489
|
-
|
|
490
|
-
// Wait for effect to run
|
|
491
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
492
|
-
|
|
493
|
-
expect(changeDetectionRuns).toBeGreaterThan(initialRuns);
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
describe("Frontend Tools Support", () => {
|
|
498
|
-
it("should process frontend tools correctly", () => {
|
|
499
|
-
const calculateTool: AngularFrontendTool = {
|
|
500
|
-
name: "calculate",
|
|
501
|
-
description: "Perform calculations",
|
|
502
|
-
parameters: z.object({
|
|
503
|
-
expression: z.string(),
|
|
504
|
-
}),
|
|
505
|
-
handler: async (args) => {
|
|
506
|
-
return eval(args.expression);
|
|
507
|
-
},
|
|
508
|
-
};
|
|
509
|
-
|
|
510
|
-
TestBed.resetTestingModule();
|
|
511
|
-
TestBed.configureTestingModule({
|
|
512
|
-
providers: [
|
|
513
|
-
provideCopilotKit({
|
|
514
|
-
frontendTools: [calculateTool],
|
|
515
|
-
}),
|
|
516
|
-
],
|
|
517
|
-
});
|
|
518
|
-
const serviceWithTools = TestBed.inject(CopilotKitService);
|
|
519
|
-
|
|
520
|
-
expect(serviceWithTools.frontendTools()).toEqual([calculateTool]);
|
|
521
|
-
expect(serviceWithTools.copilotkit.tools["calculate"]).toBeDefined();
|
|
522
|
-
expect(serviceWithTools.copilotkit.tools["calculate"].name).toBe(
|
|
523
|
-
"calculate"
|
|
524
|
-
);
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
it("should handle frontend tools with render components", () => {
|
|
528
|
-
@Component({
|
|
529
|
-
selector: "app-tool-render",
|
|
530
|
-
template: "<div>Tool Render</div>",
|
|
531
|
-
standalone: true,
|
|
532
|
-
})
|
|
533
|
-
class ToolRenderComponent {}
|
|
534
|
-
|
|
535
|
-
const toolWithRender: AngularFrontendTool = {
|
|
536
|
-
name: "toolWithRender",
|
|
537
|
-
description: "Tool with render",
|
|
538
|
-
parameters: z.object({
|
|
539
|
-
message: z.string(),
|
|
540
|
-
}),
|
|
541
|
-
handler: async (args) => args.message,
|
|
542
|
-
render: ToolRenderComponent,
|
|
543
|
-
};
|
|
544
|
-
|
|
545
|
-
TestBed.resetTestingModule();
|
|
546
|
-
TestBed.configureTestingModule({
|
|
547
|
-
providers: [
|
|
548
|
-
provideCopilotKit({
|
|
549
|
-
frontendTools: [toolWithRender],
|
|
550
|
-
}),
|
|
551
|
-
],
|
|
552
|
-
});
|
|
553
|
-
const serviceWithTools = TestBed.inject(CopilotKitService);
|
|
554
|
-
|
|
555
|
-
const renderToolCalls = serviceWithTools.renderToolCalls();
|
|
556
|
-
const toolRender = renderToolCalls.find(
|
|
557
|
-
(r) => r.name === "toolWithRender"
|
|
558
|
-
);
|
|
559
|
-
expect(toolRender).toBeDefined();
|
|
560
|
-
expect(toolRender?.render).toBe(ToolRenderComponent);
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
it("should warn when frontend tools array changes", () => {
|
|
564
|
-
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation();
|
|
565
|
-
const initialTools: AngularFrontendTool[] = [];
|
|
566
|
-
|
|
567
|
-
TestBed.resetTestingModule();
|
|
568
|
-
TestBed.configureTestingModule({
|
|
569
|
-
providers: [
|
|
570
|
-
provideCopilotKit({
|
|
571
|
-
frontendTools: initialTools,
|
|
572
|
-
}),
|
|
573
|
-
],
|
|
574
|
-
});
|
|
575
|
-
const serviceWithTools = TestBed.inject(CopilotKitService);
|
|
576
|
-
|
|
577
|
-
const newTools: AngularFrontendTool[] = [];
|
|
578
|
-
serviceWithTools.setFrontendTools(newTools);
|
|
579
|
-
|
|
580
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
581
|
-
"frontendTools must be a stable array. To add/remove tools dynamically, use dynamic tool registration."
|
|
582
|
-
);
|
|
583
|
-
consoleErrorSpy.mockRestore();
|
|
584
|
-
});
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
describe("Human-in-the-Loop Support", () => {
|
|
588
|
-
it("should process human-in-the-loop tools correctly", () => {
|
|
589
|
-
@Component({
|
|
590
|
-
selector: "app-approval",
|
|
591
|
-
template: "<div>Approval Component</div>",
|
|
592
|
-
standalone: true,
|
|
593
|
-
})
|
|
594
|
-
class ApprovalComponent {}
|
|
595
|
-
|
|
596
|
-
const approvalTool: AngularHumanInTheLoop = {
|
|
597
|
-
name: "requestApproval",
|
|
598
|
-
description: "Request user approval",
|
|
599
|
-
parameters: z.object({
|
|
600
|
-
action: z.string(),
|
|
601
|
-
reason: z.string(),
|
|
602
|
-
}),
|
|
603
|
-
render: ApprovalComponent,
|
|
604
|
-
};
|
|
605
|
-
|
|
606
|
-
TestBed.resetTestingModule();
|
|
607
|
-
TestBed.configureTestingModule({
|
|
608
|
-
providers: [
|
|
609
|
-
provideCopilotKit({
|
|
610
|
-
humanInTheLoop: [approvalTool],
|
|
611
|
-
}),
|
|
612
|
-
],
|
|
613
|
-
});
|
|
614
|
-
const serviceWithTools = TestBed.inject(CopilotKitService);
|
|
615
|
-
|
|
616
|
-
expect(serviceWithTools.humanInTheLoop()).toEqual([approvalTool]);
|
|
617
|
-
expect(
|
|
618
|
-
serviceWithTools.copilotkit.tools["requestApproval"]
|
|
619
|
-
).toBeDefined();
|
|
620
|
-
expect(serviceWithTools.copilotkit.tools["requestApproval"].name).toBe(
|
|
621
|
-
"requestApproval"
|
|
622
|
-
);
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
it("should create placeholder handlers for human-in-the-loop tools", async () => {
|
|
626
|
-
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation();
|
|
627
|
-
|
|
628
|
-
@Component({
|
|
629
|
-
selector: "app-input",
|
|
630
|
-
template: "<div>Input Component</div>",
|
|
631
|
-
standalone: true,
|
|
632
|
-
})
|
|
633
|
-
class InputComponent {}
|
|
634
|
-
|
|
635
|
-
const inputTool: AngularHumanInTheLoop = {
|
|
636
|
-
name: "getUserInput",
|
|
637
|
-
description: "Get user input",
|
|
638
|
-
parameters: z.object({
|
|
639
|
-
prompt: z.string(),
|
|
640
|
-
}),
|
|
641
|
-
render: InputComponent,
|
|
642
|
-
};
|
|
643
|
-
|
|
644
|
-
TestBed.resetTestingModule();
|
|
645
|
-
TestBed.configureTestingModule({
|
|
646
|
-
providers: [
|
|
647
|
-
provideCopilotKit({
|
|
648
|
-
humanInTheLoop: [inputTool],
|
|
649
|
-
}),
|
|
650
|
-
],
|
|
651
|
-
});
|
|
652
|
-
const serviceWithTools = TestBed.inject(CopilotKitService);
|
|
653
|
-
|
|
654
|
-
const tool = serviceWithTools.copilotkit.tools["getUserInput"];
|
|
655
|
-
expect(tool.handler).toBeDefined();
|
|
656
|
-
|
|
657
|
-
const result = await tool.handler({ prompt: "Enter value" });
|
|
658
|
-
expect(result).toBeUndefined();
|
|
659
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
660
|
-
"Human-in-the-loop tool 'getUserInput' called but no interactive handler is set up."
|
|
661
|
-
);
|
|
662
|
-
consoleWarnSpy.mockRestore();
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
it("should add render components for human-in-the-loop tools", () => {
|
|
666
|
-
@Component({
|
|
667
|
-
selector: "app-confirm",
|
|
668
|
-
template: "<div>Confirm Component</div>",
|
|
669
|
-
standalone: true,
|
|
670
|
-
})
|
|
671
|
-
class ConfirmComponent {}
|
|
672
|
-
|
|
673
|
-
const confirmTool: AngularHumanInTheLoop = {
|
|
674
|
-
name: "confirmAction",
|
|
675
|
-
description: "Confirm action",
|
|
676
|
-
parameters: z.object({
|
|
677
|
-
message: z.string(),
|
|
678
|
-
}),
|
|
679
|
-
render: ConfirmComponent,
|
|
680
|
-
};
|
|
681
|
-
|
|
682
|
-
TestBed.resetTestingModule();
|
|
683
|
-
TestBed.configureTestingModule({
|
|
684
|
-
providers: [
|
|
685
|
-
provideCopilotKit({
|
|
686
|
-
humanInTheLoop: [confirmTool],
|
|
687
|
-
}),
|
|
688
|
-
],
|
|
689
|
-
});
|
|
690
|
-
const serviceWithTools = TestBed.inject(CopilotKitService);
|
|
691
|
-
|
|
692
|
-
const renderToolCalls = serviceWithTools.renderToolCalls();
|
|
693
|
-
const confirmRender = renderToolCalls.find(
|
|
694
|
-
(r) => r.name === "confirmAction"
|
|
695
|
-
);
|
|
696
|
-
expect(confirmRender).toBeDefined();
|
|
697
|
-
expect(confirmRender?.render).toBe(ConfirmComponent);
|
|
698
|
-
expect(confirmRender?.args).toBe(confirmTool.parameters);
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
it("should warn when human-in-the-loop array changes", () => {
|
|
702
|
-
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation();
|
|
703
|
-
const initialTools: AngularHumanInTheLoop[] = [];
|
|
704
|
-
|
|
705
|
-
TestBed.resetTestingModule();
|
|
706
|
-
TestBed.configureTestingModule({
|
|
707
|
-
providers: [
|
|
708
|
-
provideCopilotKit({
|
|
709
|
-
humanInTheLoop: initialTools,
|
|
710
|
-
}),
|
|
711
|
-
],
|
|
712
|
-
});
|
|
713
|
-
const serviceWithTools = TestBed.inject(CopilotKitService);
|
|
714
|
-
|
|
715
|
-
const newTools: AngularHumanInTheLoop[] = [];
|
|
716
|
-
serviceWithTools.setHumanInTheLoop(newTools);
|
|
717
|
-
|
|
718
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
719
|
-
"humanInTheLoop must be a stable array. To add/remove human-in-the-loop tools dynamically, use dynamic tool registration."
|
|
720
|
-
);
|
|
721
|
-
consoleErrorSpy.mockRestore();
|
|
722
|
-
});
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
describe("Agent ID Constraints", () => {
|
|
726
|
-
it("should handle frontend tools with agentId", () => {
|
|
727
|
-
const globalTool: AngularFrontendTool = {
|
|
728
|
-
name: "globalTool",
|
|
729
|
-
description: "Available to all agents",
|
|
730
|
-
handler: async () => "global result",
|
|
731
|
-
};
|
|
732
|
-
|
|
733
|
-
const agent1Tool: AngularFrontendTool = {
|
|
734
|
-
name: "agent1Tool",
|
|
735
|
-
description: "Only for agent1",
|
|
736
|
-
handler: async () => "agent1 result",
|
|
737
|
-
agentId: "agent1",
|
|
738
|
-
};
|
|
739
|
-
|
|
740
|
-
const agent2Tool: AngularFrontendTool = {
|
|
741
|
-
name: "agent2Tool",
|
|
742
|
-
description: "Only for agent2",
|
|
743
|
-
handler: async () => "agent2 result",
|
|
744
|
-
agentId: "agent2",
|
|
745
|
-
};
|
|
746
|
-
|
|
747
|
-
TestBed.resetTestingModule();
|
|
748
|
-
TestBed.configureTestingModule({
|
|
749
|
-
providers: [
|
|
750
|
-
provideCopilotKit({
|
|
751
|
-
frontendTools: [globalTool, agent1Tool, agent2Tool],
|
|
752
|
-
}),
|
|
753
|
-
],
|
|
754
|
-
});
|
|
755
|
-
const serviceWithTools = TestBed.inject(CopilotKitService);
|
|
756
|
-
|
|
757
|
-
// Check all tools are registered
|
|
758
|
-
expect(serviceWithTools.copilotkit.tools["globalTool"]).toBeDefined();
|
|
759
|
-
expect(serviceWithTools.copilotkit.tools["agent1Tool"]).toBeDefined();
|
|
760
|
-
expect(serviceWithTools.copilotkit.tools["agent2Tool"]).toBeDefined();
|
|
761
|
-
|
|
762
|
-
// Check agentId is preserved
|
|
763
|
-
expect(
|
|
764
|
-
serviceWithTools.copilotkit.tools["globalTool"].agentId
|
|
765
|
-
).toBeUndefined();
|
|
766
|
-
expect(serviceWithTools.copilotkit.tools["agent1Tool"].agentId).toBe(
|
|
767
|
-
"agent1"
|
|
768
|
-
);
|
|
769
|
-
expect(serviceWithTools.copilotkit.tools["agent2Tool"].agentId).toBe(
|
|
770
|
-
"agent2"
|
|
771
|
-
);
|
|
772
|
-
});
|
|
773
|
-
|
|
774
|
-
it("should handle render tool calls with agentId", () => {
|
|
775
|
-
@Component({
|
|
776
|
-
selector: "app-global-render",
|
|
777
|
-
template: "<div>Global Render</div>",
|
|
778
|
-
standalone: true,
|
|
779
|
-
})
|
|
780
|
-
class GlobalRenderComponent {}
|
|
781
|
-
|
|
782
|
-
@Component({
|
|
783
|
-
selector: "app-agent1-render",
|
|
784
|
-
template: "<div>Agent1 Render</div>",
|
|
785
|
-
standalone: true,
|
|
786
|
-
})
|
|
787
|
-
class Agent1RenderComponent {}
|
|
788
|
-
|
|
789
|
-
const globalRenderTool = {
|
|
790
|
-
name: "globalRender",
|
|
791
|
-
args: z.object({ data: z.string() }),
|
|
792
|
-
render: GlobalRenderComponent,
|
|
793
|
-
};
|
|
794
|
-
|
|
795
|
-
const agent1RenderTool = {
|
|
796
|
-
name: "agent1Render",
|
|
797
|
-
args: z.object({ data: z.string() }),
|
|
798
|
-
render: Agent1RenderComponent,
|
|
799
|
-
agentId: "agent1",
|
|
800
|
-
};
|
|
801
|
-
|
|
802
|
-
TestBed.resetTestingModule();
|
|
803
|
-
TestBed.configureTestingModule({
|
|
804
|
-
providers: [
|
|
805
|
-
provideCopilotKit({
|
|
806
|
-
renderToolCalls: [globalRenderTool, agent1RenderTool],
|
|
807
|
-
}),
|
|
808
|
-
],
|
|
809
|
-
});
|
|
810
|
-
const serviceWithTools = TestBed.inject(CopilotKitService);
|
|
811
|
-
|
|
812
|
-
const renderToolCalls = serviceWithTools.renderToolCalls();
|
|
813
|
-
const globalRender = renderToolCalls.find(
|
|
814
|
-
(r) => r.name === "globalRender"
|
|
815
|
-
);
|
|
816
|
-
const agent1Render = renderToolCalls.find(
|
|
817
|
-
(r) => r.name === "agent1Render"
|
|
818
|
-
);
|
|
819
|
-
expect(globalRender).toBeDefined();
|
|
820
|
-
expect(agent1Render).toBeDefined();
|
|
821
|
-
|
|
822
|
-
// Check agentId is preserved in render tool calls
|
|
823
|
-
expect(globalRender?.agentId).toBeUndefined();
|
|
824
|
-
expect(agent1Render?.agentId).toBe("agent1");
|
|
825
|
-
});
|
|
826
|
-
|
|
827
|
-
it("should handle frontend tools with render and agentId", () => {
|
|
828
|
-
@Component({
|
|
829
|
-
selector: "app-agent-specific-render",
|
|
830
|
-
template: "<div>Agent Specific Render</div>",
|
|
831
|
-
standalone: true,
|
|
832
|
-
})
|
|
833
|
-
class AgentSpecificRenderComponent {}
|
|
834
|
-
|
|
835
|
-
const agentSpecificTool: AngularFrontendTool = {
|
|
836
|
-
name: "agentSpecificTool",
|
|
837
|
-
description: "Tool for specific agent",
|
|
838
|
-
parameters: z.object({ value: z.string() }),
|
|
839
|
-
handler: async (args) => args.value,
|
|
840
|
-
render: AgentSpecificRenderComponent,
|
|
841
|
-
agentId: "specificAgent",
|
|
842
|
-
};
|
|
843
|
-
|
|
844
|
-
TestBed.resetTestingModule();
|
|
845
|
-
TestBed.configureTestingModule({
|
|
846
|
-
providers: [
|
|
847
|
-
provideCopilotKit({
|
|
848
|
-
frontendTools: [agentSpecificTool],
|
|
849
|
-
}),
|
|
850
|
-
],
|
|
851
|
-
});
|
|
852
|
-
const serviceWithTools = TestBed.inject(CopilotKitService);
|
|
853
|
-
|
|
854
|
-
// Check tool is registered with agentId
|
|
855
|
-
expect(
|
|
856
|
-
serviceWithTools.copilotkit.tools["agentSpecificTool"]
|
|
857
|
-
).toBeDefined();
|
|
858
|
-
expect(
|
|
859
|
-
serviceWithTools.copilotkit.tools["agentSpecificTool"].agentId
|
|
860
|
-
).toBe("specificAgent");
|
|
861
|
-
|
|
862
|
-
// Check render is registered with agentId
|
|
863
|
-
const renderToolCalls = serviceWithTools.renderToolCalls();
|
|
864
|
-
const agentRender = renderToolCalls.find(
|
|
865
|
-
(r) => r.name === "agentSpecificTool"
|
|
866
|
-
);
|
|
867
|
-
expect(agentRender).toBeDefined();
|
|
868
|
-
expect(agentRender?.agentId).toBe("specificAgent");
|
|
869
|
-
expect(agentRender?.render).toBe(AgentSpecificRenderComponent);
|
|
870
|
-
});
|
|
871
|
-
|
|
872
|
-
it("should handle human-in-the-loop tools with agentId", () => {
|
|
873
|
-
@Component({
|
|
874
|
-
selector: "app-agent-approval",
|
|
875
|
-
template: "<div>Agent Approval</div>",
|
|
876
|
-
standalone: true,
|
|
877
|
-
})
|
|
878
|
-
class AgentApprovalComponent {}
|
|
879
|
-
|
|
880
|
-
const agentApprovalTool: AngularHumanInTheLoop = {
|
|
881
|
-
name: "agentApproval",
|
|
882
|
-
description: "Approval for specific agent",
|
|
883
|
-
parameters: z.object({ question: z.string() }),
|
|
884
|
-
render: AgentApprovalComponent,
|
|
885
|
-
agentId: "approvalAgent",
|
|
886
|
-
};
|
|
887
|
-
|
|
888
|
-
TestBed.resetTestingModule();
|
|
889
|
-
TestBed.configureTestingModule({
|
|
890
|
-
providers: [
|
|
891
|
-
provideCopilotKit({
|
|
892
|
-
humanInTheLoop: [agentApprovalTool],
|
|
893
|
-
}),
|
|
894
|
-
],
|
|
895
|
-
});
|
|
896
|
-
const serviceWithTools = TestBed.inject(CopilotKitService);
|
|
897
|
-
|
|
898
|
-
// Check tool is registered with agentId
|
|
899
|
-
expect(serviceWithTools.copilotkit.tools["agentApproval"]).toBeDefined();
|
|
900
|
-
expect(serviceWithTools.copilotkit.tools["agentApproval"].agentId).toBe(
|
|
901
|
-
"approvalAgent"
|
|
902
|
-
);
|
|
903
|
-
|
|
904
|
-
// Check render is registered with agentId
|
|
905
|
-
const renderToolCalls = serviceWithTools.renderToolCalls();
|
|
906
|
-
const approvalRender = renderToolCalls.find(
|
|
907
|
-
(r) => r.name === "agentApproval"
|
|
908
|
-
);
|
|
909
|
-
expect(approvalRender).toBeDefined();
|
|
910
|
-
expect(approvalRender?.agentId).toBe("approvalAgent");
|
|
911
|
-
expect(approvalRender?.render).toBe(AgentApprovalComponent);
|
|
912
|
-
});
|
|
913
|
-
|
|
914
|
-
it("should handle mixed tools with and without agentId", () => {
|
|
915
|
-
@Component({
|
|
916
|
-
selector: "app-mixed-render",
|
|
917
|
-
template: "<div>Mixed Render</div>",
|
|
918
|
-
standalone: true,
|
|
919
|
-
})
|
|
920
|
-
class MixedRenderComponent {}
|
|
921
|
-
|
|
922
|
-
const globalTool: AngularFrontendTool = {
|
|
923
|
-
name: "globalTool",
|
|
924
|
-
handler: async () => "global",
|
|
925
|
-
};
|
|
926
|
-
|
|
927
|
-
const specificTool: AngularFrontendTool = {
|
|
928
|
-
name: "specificTool",
|
|
929
|
-
parameters: z.object({ value: z.string() }),
|
|
930
|
-
handler: async () => "specific",
|
|
931
|
-
render: MixedRenderComponent,
|
|
932
|
-
agentId: "specificAgent",
|
|
933
|
-
};
|
|
934
|
-
|
|
935
|
-
const hitlTool: AngularHumanInTheLoop = {
|
|
936
|
-
name: "hitlTool",
|
|
937
|
-
parameters: z.object({ prompt: z.string() }),
|
|
938
|
-
render: MixedRenderComponent,
|
|
939
|
-
agentId: "hitlAgent",
|
|
940
|
-
};
|
|
941
|
-
|
|
942
|
-
TestBed.resetTestingModule();
|
|
943
|
-
TestBed.configureTestingModule({
|
|
944
|
-
providers: [
|
|
945
|
-
provideCopilotKit({
|
|
946
|
-
frontendTools: [globalTool, specificTool],
|
|
947
|
-
humanInTheLoop: [hitlTool],
|
|
948
|
-
}),
|
|
949
|
-
],
|
|
950
|
-
});
|
|
951
|
-
const serviceWithTools = TestBed.inject(CopilotKitService);
|
|
952
|
-
|
|
953
|
-
// Check tools registration with correct agentId
|
|
954
|
-
expect(
|
|
955
|
-
serviceWithTools.copilotkit.tools["globalTool"].agentId
|
|
956
|
-
).toBeUndefined();
|
|
957
|
-
expect(serviceWithTools.copilotkit.tools["specificTool"].agentId).toBe(
|
|
958
|
-
"specificAgent"
|
|
959
|
-
);
|
|
960
|
-
expect(serviceWithTools.copilotkit.tools["hitlTool"].agentId).toBe(
|
|
961
|
-
"hitlAgent"
|
|
962
|
-
);
|
|
963
|
-
|
|
964
|
-
// Check render registration
|
|
965
|
-
const renderToolCalls = serviceWithTools.renderToolCalls();
|
|
966
|
-
const globalRender = renderToolCalls.find((r) => r.name === "globalTool");
|
|
967
|
-
const specificRender = renderToolCalls.find(
|
|
968
|
-
(r) => r.name === "specificTool"
|
|
969
|
-
);
|
|
970
|
-
const hitlRender = renderToolCalls.find((r) => r.name === "hitlTool");
|
|
971
|
-
expect(globalRender).toBeUndefined(); // No render
|
|
972
|
-
expect(specificRender?.agentId).toBe("specificAgent");
|
|
973
|
-
expect(hitlRender?.agentId).toBe("hitlAgent");
|
|
974
|
-
});
|
|
975
|
-
});
|
|
976
|
-
|
|
977
|
-
describe("Combined Tools and Renders", () => {
|
|
978
|
-
it("should combine all tools and render calls correctly", () => {
|
|
979
|
-
@Component({
|
|
980
|
-
selector: "app-frontend-render",
|
|
981
|
-
template: "<div>Frontend Render</div>",
|
|
982
|
-
standalone: true,
|
|
983
|
-
})
|
|
984
|
-
class FrontendRenderComponent {}
|
|
985
|
-
|
|
986
|
-
@Component({
|
|
987
|
-
selector: "app-hitl-render",
|
|
988
|
-
template: "<div>HITL Render</div>",
|
|
989
|
-
standalone: true,
|
|
990
|
-
})
|
|
991
|
-
class HITLRenderComponent {}
|
|
992
|
-
|
|
993
|
-
@Component({
|
|
994
|
-
selector: "app-custom-render",
|
|
995
|
-
template: "<div>Custom Render</div>",
|
|
996
|
-
standalone: true,
|
|
997
|
-
})
|
|
998
|
-
class CustomRenderComponent {}
|
|
999
|
-
|
|
1000
|
-
const frontendTool: AngularFrontendTool = {
|
|
1001
|
-
name: "frontendTool",
|
|
1002
|
-
parameters: z.object({ value: z.string() }),
|
|
1003
|
-
handler: async (args) => args.value,
|
|
1004
|
-
render: FrontendRenderComponent,
|
|
1005
|
-
};
|
|
1006
|
-
|
|
1007
|
-
const hitlTool: AngularHumanInTheLoop = {
|
|
1008
|
-
name: "hitlTool",
|
|
1009
|
-
parameters: z.object({ prompt: z.string() }),
|
|
1010
|
-
render: HITLRenderComponent,
|
|
1011
|
-
};
|
|
1012
|
-
|
|
1013
|
-
const customRenderTool = {
|
|
1014
|
-
name: "customTool",
|
|
1015
|
-
args: z.object({ data: z.string() }),
|
|
1016
|
-
render: CustomRenderComponent,
|
|
1017
|
-
};
|
|
1018
|
-
|
|
1019
|
-
TestBed.resetTestingModule();
|
|
1020
|
-
TestBed.configureTestingModule({
|
|
1021
|
-
providers: [
|
|
1022
|
-
provideCopilotKit({
|
|
1023
|
-
frontendTools: [frontendTool],
|
|
1024
|
-
humanInTheLoop: [hitlTool],
|
|
1025
|
-
renderToolCalls: [customRenderTool],
|
|
1026
|
-
}),
|
|
1027
|
-
],
|
|
1028
|
-
});
|
|
1029
|
-
const serviceWithTools = TestBed.inject(CopilotKitService);
|
|
1030
|
-
|
|
1031
|
-
// Check all tools are registered
|
|
1032
|
-
expect(serviceWithTools.copilotkit.tools["frontendTool"]).toBeDefined();
|
|
1033
|
-
expect(serviceWithTools.copilotkit.tools["hitlTool"]).toBeDefined();
|
|
1034
|
-
|
|
1035
|
-
// Check all render calls are combined
|
|
1036
|
-
const renderToolCalls = serviceWithTools.renderToolCalls();
|
|
1037
|
-
const frontendRender = renderToolCalls.find(
|
|
1038
|
-
(r) => r.name === "frontendTool"
|
|
1039
|
-
);
|
|
1040
|
-
const hitlRender = renderToolCalls.find((r) => r.name === "hitlTool");
|
|
1041
|
-
const customRender = renderToolCalls.find((r) => r.name === "customTool");
|
|
1042
|
-
expect(frontendRender).toBeDefined();
|
|
1043
|
-
expect(hitlRender).toBeDefined();
|
|
1044
|
-
expect(customRender).toBeDefined();
|
|
1045
|
-
|
|
1046
|
-
expect(frontendRender?.render).toBe(FrontendRenderComponent);
|
|
1047
|
-
expect(hitlRender?.render).toBe(HITLRenderComponent);
|
|
1048
|
-
expect(customRender?.render).toBe(CustomRenderComponent);
|
|
1049
|
-
});
|
|
1050
|
-
});
|
|
1051
|
-
});
|