@copilotkitnext/angular 0.0.2 → 0.0.5

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.
Files changed (173) hide show
  1. package/README.md +3 -3
  2. package/dist/README.md +3 -3
  3. package/dist/components/chat/copilot-chat-assistant-message.component.d.ts +10 -10
  4. package/dist/components/chat/copilot-chat-message-view.component.d.ts +42 -42
  5. package/dist/components/chat/copilot-chat-view.component.d.ts +14 -14
  6. package/dist/core/copilotkit.providers.d.ts +1 -1
  7. package/dist/core/copilotkit.service.d.ts +5 -5
  8. package/dist/core/copilotkit.types.d.ts +8 -10
  9. package/dist/directives/copilotkit-frontend-tool.directive.d.ts +1 -1
  10. package/dist/esm2022/components/chat/copilot-chat-assistant-message-buttons.component.mjs +384 -0
  11. package/dist/esm2022/components/chat/copilot-chat-assistant-message-renderer.component.mjs +286 -0
  12. package/dist/esm2022/components/chat/copilot-chat-assistant-message-toolbar.component.mjs +27 -0
  13. package/dist/esm2022/components/chat/copilot-chat-assistant-message.component.mjs +433 -0
  14. package/dist/esm2022/components/chat/copilot-chat-assistant-message.types.mjs +2 -0
  15. package/dist/esm2022/components/chat/copilot-chat-audio-recorder.component.mjs +202 -0
  16. package/dist/esm2022/components/chat/copilot-chat-buttons.component.mjs +321 -0
  17. package/dist/esm2022/components/chat/copilot-chat-input-defaults.mjs +38 -0
  18. package/dist/esm2022/components/chat/copilot-chat-input.component.mjs +666 -0
  19. package/dist/esm2022/components/chat/copilot-chat-input.types.mjs +10 -0
  20. package/dist/esm2022/components/chat/copilot-chat-message-view-cursor.component.mjs +45 -0
  21. package/dist/esm2022/components/chat/copilot-chat-message-view.component.mjs +296 -0
  22. package/dist/esm2022/components/chat/copilot-chat-message-view.types.mjs +2 -0
  23. package/dist/esm2022/components/chat/copilot-chat-textarea.component.mjs +188 -0
  24. package/dist/esm2022/components/chat/copilot-chat-tool-calls-view.component.mjs +216 -0
  25. package/dist/esm2022/components/chat/copilot-chat-toolbar.component.mjs +25 -0
  26. package/dist/esm2022/components/chat/copilot-chat-tools-menu.component.mjs +199 -0
  27. package/dist/esm2022/components/chat/copilot-chat-user-message-branch-navigation.component.mjs +137 -0
  28. package/dist/esm2022/components/chat/copilot-chat-user-message-buttons.component.mjs +207 -0
  29. package/dist/esm2022/components/chat/copilot-chat-user-message-renderer.component.mjs +35 -0
  30. package/dist/esm2022/components/chat/copilot-chat-user-message-toolbar.component.mjs +34 -0
  31. package/dist/esm2022/components/chat/copilot-chat-user-message.component.mjs +341 -0
  32. package/dist/esm2022/components/chat/copilot-chat-user-message.types.mjs +2 -0
  33. package/dist/esm2022/components/chat/copilot-chat-view-disclaimer.component.mjs +52 -0
  34. package/dist/esm2022/components/chat/copilot-chat-view-feather.component.mjs +55 -0
  35. package/dist/esm2022/components/chat/copilot-chat-view-handlers.service.mjs +19 -0
  36. package/dist/esm2022/components/chat/copilot-chat-view-input-container.component.mjs +110 -0
  37. package/dist/esm2022/components/chat/copilot-chat-view-scroll-to-bottom-button.component.mjs +93 -0
  38. package/dist/esm2022/components/chat/copilot-chat-view-scroll-view.component.mjs +443 -0
  39. package/dist/esm2022/components/chat/copilot-chat-view.component.mjs +479 -0
  40. package/dist/esm2022/components/chat/copilot-chat-view.types.mjs +2 -0
  41. package/dist/esm2022/components/chat/copilot-chat.component.mjs +214 -0
  42. package/dist/esm2022/components/copilotkit-tool-render.component.mjs +153 -0
  43. package/dist/esm2022/copilotkitnext-angular.mjs +5 -0
  44. package/dist/esm2022/core/chat-configuration/chat-configuration.providers.mjs +65 -0
  45. package/dist/esm2022/core/chat-configuration/chat-configuration.service.mjs +145 -0
  46. package/dist/esm2022/core/chat-configuration/chat-configuration.types.mjs +26 -0
  47. package/dist/esm2022/core/copilotkit.providers.mjs +34 -0
  48. package/dist/esm2022/core/copilotkit.service.mjs +426 -0
  49. package/dist/esm2022/core/copilotkit.types.mjs +13 -0
  50. package/dist/esm2022/directives/copilotkit-agent-context.directive.mjs +130 -0
  51. package/dist/esm2022/directives/copilotkit-agent.directive.mjs +217 -0
  52. package/dist/esm2022/directives/copilotkit-chat-config.directive.mjs +218 -0
  53. package/dist/esm2022/directives/copilotkit-config.directive.mjs +94 -0
  54. package/dist/esm2022/directives/copilotkit-frontend-tool.directive.mjs +128 -0
  55. package/dist/esm2022/directives/copilotkit-human-in-the-loop.directive.mjs +265 -0
  56. package/dist/esm2022/directives/stick-to-bottom.directive.mjs +181 -0
  57. package/dist/esm2022/index.mjs +70 -0
  58. package/dist/esm2022/lib/directives/tooltip.directive.mjs +211 -0
  59. package/dist/esm2022/lib/slots/copilot-slot.component.mjs +144 -0
  60. package/dist/esm2022/lib/slots/slot.types.mjs +6 -0
  61. package/dist/esm2022/lib/slots/slot.utils.mjs +222 -0
  62. package/dist/esm2022/lib/utils.mjs +10 -0
  63. package/dist/esm2022/services/resize-observer.service.mjs +152 -0
  64. package/dist/esm2022/services/scroll-position.service.mjs +124 -0
  65. package/dist/esm2022/types/frontend-tool.mjs +2 -0
  66. package/dist/esm2022/types/human-in-the-loop.mjs +2 -0
  67. package/dist/esm2022/utils/agent-context.utils.mjs +114 -0
  68. package/dist/esm2022/utils/agent.utils.mjs +204 -0
  69. package/dist/esm2022/utils/chat-config.utils.mjs +186 -0
  70. package/dist/esm2022/utils/copilotkit.utils.mjs +20 -0
  71. package/dist/esm2022/utils/frontend-tool.utils.mjs +224 -0
  72. package/dist/esm2022/utils/human-in-the-loop.utils.mjs +293 -0
  73. package/dist/fesm2022/copilotkitnext-angular.mjs +174 -187
  74. package/dist/fesm2022/copilotkitnext-angular.mjs.map +1 -1
  75. package/dist/utils/frontend-tool.utils.d.ts +1 -1
  76. package/package.json +23 -20
  77. package/vitest.config.mts +32 -21
  78. package/.turbo/turbo-build.log +0 -38
  79. package/.turbo/turbo-check-types.log +0 -0
  80. package/.turbo/turbo-test.log +0 -71
  81. package/ng-package.json +0 -19
  82. package/src/components/chat/__tests__/copilot-chat-assistant-message.component.spec.ts +0 -282
  83. package/src/components/chat/__tests__/copilot-chat-input.component.spec.ts +0 -419
  84. package/src/components/chat/__tests__/copilot-chat-message-view.component.spec.ts +0 -372
  85. package/src/components/chat/__tests__/copilot-chat-user-message.component.spec.ts +0 -249
  86. package/src/components/chat/copilot-chat-assistant-message-buttons.component.ts +0 -292
  87. package/src/components/chat/copilot-chat-assistant-message-renderer.component.ts +0 -472
  88. package/src/components/chat/copilot-chat-assistant-message-toolbar.component.ts +0 -29
  89. package/src/components/chat/copilot-chat-assistant-message.component.ts +0 -463
  90. package/src/components/chat/copilot-chat-assistant-message.types.ts +0 -50
  91. package/src/components/chat/copilot-chat-audio-recorder.component.ts +0 -241
  92. package/src/components/chat/copilot-chat-buttons.component.ts +0 -308
  93. package/src/components/chat/copilot-chat-buttons.component.ts.bak +0 -471
  94. package/src/components/chat/copilot-chat-input-defaults.ts +0 -47
  95. package/src/components/chat/copilot-chat-input.component.ts +0 -512
  96. package/src/components/chat/copilot-chat-input.types.ts +0 -148
  97. package/src/components/chat/copilot-chat-message-view-cursor.component.ts +0 -51
  98. package/src/components/chat/copilot-chat-message-view.component.ts +0 -233
  99. package/src/components/chat/copilot-chat-message-view.types.ts +0 -39
  100. package/src/components/chat/copilot-chat-textarea.component.ts +0 -220
  101. package/src/components/chat/copilot-chat-tool-calls-view.component.ts +0 -261
  102. package/src/components/chat/copilot-chat-toolbar.component.ts +0 -35
  103. package/src/components/chat/copilot-chat-tools-menu.component.ts +0 -185
  104. package/src/components/chat/copilot-chat-user-message-branch-navigation.component.ts +0 -121
  105. package/src/components/chat/copilot-chat-user-message-buttons.component.ts +0 -170
  106. package/src/components/chat/copilot-chat-user-message-renderer.component.ts +0 -37
  107. package/src/components/chat/copilot-chat-user-message-toolbar.component.ts +0 -37
  108. package/src/components/chat/copilot-chat-user-message.component.ts +0 -247
  109. package/src/components/chat/copilot-chat-user-message.types.ts +0 -42
  110. package/src/components/chat/copilot-chat-view-disclaimer.component.ts +0 -51
  111. package/src/components/chat/copilot-chat-view-feather.component.ts +0 -47
  112. package/src/components/chat/copilot-chat-view-handlers.service.ts +0 -14
  113. package/src/components/chat/copilot-chat-view-input-container.component.ts +0 -87
  114. package/src/components/chat/copilot-chat-view-scroll-to-bottom-button.component.ts +0 -79
  115. package/src/components/chat/copilot-chat-view-scroll-view.component.ts +0 -322
  116. package/src/components/chat/copilot-chat-view.component.ts +0 -420
  117. package/src/components/chat/copilot-chat-view.types.ts +0 -52
  118. package/src/components/chat/copilot-chat.component.ts +0 -232
  119. package/src/components/copilotkit-tool-render.component.ts +0 -169
  120. package/src/core/__tests__/copilotkit.service.spec.ts +0 -1051
  121. package/src/core/__tests__/copilotkit.service.wildcard.spec.ts +0 -316
  122. package/src/core/chat-configuration/__tests__/chat-configuration.service.spec.ts +0 -287
  123. package/src/core/chat-configuration/chat-configuration.providers.ts +0 -71
  124. package/src/core/chat-configuration/chat-configuration.service.ts +0 -162
  125. package/src/core/chat-configuration/chat-configuration.types.ts +0 -57
  126. package/src/core/copilotkit.providers.ts +0 -59
  127. package/src/core/copilotkit.service.ts +0 -542
  128. package/src/core/copilotkit.types.ts +0 -132
  129. package/src/directives/__tests__/copilotkit-agent-context.directive.spec.ts +0 -384
  130. package/src/directives/__tests__/copilotkit-agent.directive.spec.ts +0 -253
  131. package/src/directives/__tests__/copilotkit-chat-config.directive.spec.ts +0 -385
  132. package/src/directives/__tests__/copilotkit-config.directive.spec.ts +0 -69
  133. package/src/directives/__tests__/copilotkit-frontend-tool-simple.directive.spec.ts +0 -60
  134. package/src/directives/__tests__/copilotkit-frontend-tool.directive.spec.ts +0 -108
  135. package/src/directives/__tests__/copilotkit-human-in-the-loop.directive.spec.ts +0 -452
  136. package/src/directives/copilotkit-agent-context.directive.ts +0 -138
  137. package/src/directives/copilotkit-agent.directive.ts +0 -225
  138. package/src/directives/copilotkit-chat-config.directive.ts +0 -241
  139. package/src/directives/copilotkit-config.directive.ts +0 -81
  140. package/src/directives/copilotkit-frontend-tool.directive.ts +0 -145
  141. package/src/directives/copilotkit-human-in-the-loop.directive.ts +0 -281
  142. package/src/directives/stick-to-bottom.directive.ts +0 -204
  143. package/src/index.ts +0 -105
  144. package/src/lib/directives/tooltip.directive.ts +0 -292
  145. package/src/lib/slots/__tests__/slot.utils.spec.ts +0 -377
  146. package/src/lib/slots/copilot-slot.component.ts +0 -135
  147. package/src/lib/slots/index.ts +0 -3
  148. package/src/lib/slots/slot.types.ts +0 -64
  149. package/src/lib/slots/slot.utils.ts +0 -289
  150. package/src/lib/utils.ts +0 -10
  151. package/src/public-api.ts +0 -1
  152. package/src/services/resize-observer.service.ts +0 -181
  153. package/src/services/scroll-position.service.ts +0 -169
  154. package/src/styles/globals.css +0 -266
  155. package/src/styles/index.css +0 -3
  156. package/src/test-setup.ts +0 -15
  157. package/src/testing/index.ts +0 -3
  158. package/src/testing/testing.utils.ts +0 -248
  159. package/src/types/frontend-tool.ts +0 -44
  160. package/src/types/human-in-the-loop.ts +0 -52
  161. package/src/utils/__tests__/agent.utils.spec.ts +0 -234
  162. package/src/utils/__tests__/chat-config.utils.spec.ts +0 -306
  163. package/src/utils/__tests__/frontend-tool-inject.spec.ts +0 -350
  164. package/src/utils/__tests__/frontend-tool-integration.spec.ts +0 -199
  165. package/src/utils/__tests__/frontend-tool.utils.spec.ts +0 -272
  166. package/src/utils/__tests__/human-in-the-loop.utils.spec.ts +0 -365
  167. package/src/utils/agent-context.utils.ts +0 -133
  168. package/src/utils/agent.utils.ts +0 -239
  169. package/src/utils/chat-config.utils.ts +0 -221
  170. package/src/utils/copilotkit.utils.ts +0 -20
  171. package/src/utils/frontend-tool.utils.ts +0 -266
  172. package/src/utils/human-in-the-loop.utils.ts +0 -359
  173. 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
- });