@copilotkitnext/agent 0.0.13-alpha.0

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.
@@ -0,0 +1,559 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { BasicAgent } from "../index";
3
+ import { type RunAgentInput } from "@ag-ui/client";
4
+ import { streamText } from "ai";
5
+ import { mockStreamTextResponse, finish, collectEvents } from "./test-helpers";
6
+
7
+ // Mock the ai module
8
+ vi.mock("ai", () => ({
9
+ streamText: vi.fn(),
10
+ tool: vi.fn((config) => config),
11
+ }));
12
+
13
+ // Mock the SDK clients
14
+ vi.mock("@ai-sdk/openai", () => ({
15
+ createOpenAI: vi.fn(() => (modelId: string) => ({
16
+ modelId,
17
+ provider: "openai",
18
+ })),
19
+ }));
20
+
21
+ vi.mock("@ai-sdk/anthropic", () => ({
22
+ createAnthropic: vi.fn(() => (modelId: string) => ({
23
+ modelId,
24
+ provider: "anthropic",
25
+ })),
26
+ }));
27
+
28
+ describe("Property Overrides - Edge Cases", () => {
29
+ const originalEnv = process.env;
30
+
31
+ beforeEach(() => {
32
+ vi.clearAllMocks();
33
+ process.env = { ...originalEnv };
34
+ process.env.OPENAI_API_KEY = "test-key";
35
+ process.env.ANTHROPIC_API_KEY = "test-key";
36
+ });
37
+
38
+ afterEach(() => {
39
+ process.env = originalEnv;
40
+ });
41
+
42
+ describe("Model Override", () => {
43
+ it("should override model when allowed", async () => {
44
+ const agent = new BasicAgent({
45
+ model: "openai/gpt-4o",
46
+ overridableProperties: ["model"],
47
+ });
48
+
49
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
50
+
51
+ const input: RunAgentInput = {
52
+ threadId: "thread1",
53
+ runId: "run1",
54
+ messages: [],
55
+ tools: [],
56
+ context: [],
57
+ state: {},
58
+ forwardedProps: { model: "anthropic/claude-sonnet-4.5" },
59
+ };
60
+
61
+ await collectEvents(agent["run"](input));
62
+
63
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
64
+ expect(callArgs.model.provider).toBe("anthropic");
65
+ expect(callArgs.model.modelId).toBe("claude-sonnet-4.5");
66
+ });
67
+
68
+ it("should accept LanguageModel instance for override", async () => {
69
+ const agent = new BasicAgent({
70
+ model: "openai/gpt-4o",
71
+ overridableProperties: ["model"],
72
+ });
73
+
74
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
75
+
76
+ const customModel = {
77
+ modelId: "custom-model",
78
+ provider: "custom",
79
+ };
80
+
81
+ const input: RunAgentInput = {
82
+ threadId: "thread1",
83
+ runId: "run1",
84
+ messages: [],
85
+ tools: [],
86
+ context: [],
87
+ state: {},
88
+ forwardedProps: { model: customModel },
89
+ };
90
+
91
+ await collectEvents(agent["run"](input));
92
+
93
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
94
+ expect(callArgs.model).toBe(customModel);
95
+ });
96
+
97
+ it("should ignore invalid model override types", async () => {
98
+ const agent = new BasicAgent({
99
+ model: "openai/gpt-4o",
100
+ overridableProperties: ["model"],
101
+ });
102
+
103
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
104
+
105
+ const input: RunAgentInput = {
106
+ threadId: "thread1",
107
+ runId: "run1",
108
+ messages: [],
109
+ tools: [],
110
+ context: [],
111
+ state: {},
112
+ forwardedProps: { model: 123 }, // Invalid type
113
+ };
114
+
115
+ await collectEvents(agent["run"](input));
116
+
117
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
118
+ expect(callArgs.model.modelId).toBe("gpt-4o"); // Original value
119
+ });
120
+ });
121
+
122
+ describe("ToolChoice Override", () => {
123
+ it("should override with 'auto'", async () => {
124
+ const agent = new BasicAgent({
125
+ model: "openai/gpt-4o",
126
+ toolChoice: "required",
127
+ overridableProperties: ["toolChoice"],
128
+ });
129
+
130
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
131
+
132
+ const input: RunAgentInput = {
133
+ threadId: "thread1",
134
+ runId: "run1",
135
+ messages: [],
136
+ tools: [],
137
+ context: [],
138
+ state: {},
139
+ forwardedProps: { toolChoice: "auto" },
140
+ };
141
+
142
+ await collectEvents(agent["run"](input));
143
+
144
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
145
+ expect(callArgs.toolChoice).toBe("auto");
146
+ });
147
+
148
+ it("should override with 'required'", async () => {
149
+ const agent = new BasicAgent({
150
+ model: "openai/gpt-4o",
151
+ toolChoice: "auto",
152
+ overridableProperties: ["toolChoice"],
153
+ });
154
+
155
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
156
+
157
+ const input: RunAgentInput = {
158
+ threadId: "thread1",
159
+ runId: "run1",
160
+ messages: [],
161
+ tools: [],
162
+ context: [],
163
+ state: {},
164
+ forwardedProps: { toolChoice: "required" },
165
+ };
166
+
167
+ await collectEvents(agent["run"](input));
168
+
169
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
170
+ expect(callArgs.toolChoice).toBe("required");
171
+ });
172
+
173
+ it("should override with 'none'", async () => {
174
+ const agent = new BasicAgent({
175
+ model: "openai/gpt-4o",
176
+ toolChoice: "auto",
177
+ overridableProperties: ["toolChoice"],
178
+ });
179
+
180
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
181
+
182
+ const input: RunAgentInput = {
183
+ threadId: "thread1",
184
+ runId: "run1",
185
+ messages: [],
186
+ tools: [],
187
+ context: [],
188
+ state: {},
189
+ forwardedProps: { toolChoice: "none" },
190
+ };
191
+
192
+ await collectEvents(agent["run"](input));
193
+
194
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
195
+ expect(callArgs.toolChoice).toBe("none");
196
+ });
197
+
198
+ it("should override with specific tool selection", async () => {
199
+ const agent = new BasicAgent({
200
+ model: "openai/gpt-4o",
201
+ toolChoice: "auto",
202
+ overridableProperties: ["toolChoice"],
203
+ });
204
+
205
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
206
+
207
+ const input: RunAgentInput = {
208
+ threadId: "thread1",
209
+ runId: "run1",
210
+ messages: [],
211
+ tools: [],
212
+ context: [],
213
+ state: {},
214
+ forwardedProps: { toolChoice: { type: "tool", toolName: "specificTool" } },
215
+ };
216
+
217
+ await collectEvents(agent["run"](input));
218
+
219
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
220
+ expect(callArgs.toolChoice).toEqual({ type: "tool", toolName: "specificTool" });
221
+ });
222
+
223
+ it("should ignore invalid toolChoice values", async () => {
224
+ const agent = new BasicAgent({
225
+ model: "openai/gpt-4o",
226
+ toolChoice: "auto",
227
+ overridableProperties: ["toolChoice"],
228
+ });
229
+
230
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
231
+
232
+ const input: RunAgentInput = {
233
+ threadId: "thread1",
234
+ runId: "run1",
235
+ messages: [],
236
+ tools: [],
237
+ context: [],
238
+ state: {},
239
+ forwardedProps: { toolChoice: "invalid" },
240
+ };
241
+
242
+ await collectEvents(agent["run"](input));
243
+
244
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
245
+ expect(callArgs.toolChoice).toBe("auto"); // Original value
246
+ });
247
+ });
248
+
249
+ describe("StopSequences Override", () => {
250
+ it("should override stopSequences with valid array", async () => {
251
+ const agent = new BasicAgent({
252
+ model: "openai/gpt-4o",
253
+ stopSequences: ["STOP"],
254
+ overridableProperties: ["stopSequences"],
255
+ });
256
+
257
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
258
+
259
+ const input: RunAgentInput = {
260
+ threadId: "thread1",
261
+ runId: "run1",
262
+ messages: [],
263
+ tools: [],
264
+ context: [],
265
+ state: {},
266
+ forwardedProps: { stopSequences: ["END", "FINISH"] },
267
+ };
268
+
269
+ await collectEvents(agent["run"](input));
270
+
271
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
272
+ expect(callArgs.stopSequences).toEqual(["END", "FINISH"]);
273
+ });
274
+
275
+ it("should ignore stopSequences with non-string elements", async () => {
276
+ const agent = new BasicAgent({
277
+ model: "openai/gpt-4o",
278
+ stopSequences: ["STOP"],
279
+ overridableProperties: ["stopSequences"],
280
+ });
281
+
282
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
283
+
284
+ const input: RunAgentInput = {
285
+ threadId: "thread1",
286
+ runId: "run1",
287
+ messages: [],
288
+ tools: [],
289
+ context: [],
290
+ state: {},
291
+ forwardedProps: { stopSequences: ["STOP", 123, "END"] as any },
292
+ };
293
+
294
+ await collectEvents(agent["run"](input));
295
+
296
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
297
+ expect(callArgs.stopSequences).toEqual(["STOP"]); // Original value
298
+ });
299
+
300
+ it("should ignore non-array stopSequences", async () => {
301
+ const agent = new BasicAgent({
302
+ model: "openai/gpt-4o",
303
+ stopSequences: ["STOP"],
304
+ overridableProperties: ["stopSequences"],
305
+ });
306
+
307
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
308
+
309
+ const input: RunAgentInput = {
310
+ threadId: "thread1",
311
+ runId: "run1",
312
+ messages: [],
313
+ tools: [],
314
+ context: [],
315
+ state: {},
316
+ forwardedProps: { stopSequences: "STOP" as any },
317
+ };
318
+
319
+ await collectEvents(agent["run"](input));
320
+
321
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
322
+ expect(callArgs.stopSequences).toEqual(["STOP"]); // Original value
323
+ });
324
+ });
325
+
326
+ describe("Numeric Property Overrides", () => {
327
+ it("should override all numeric properties when allowed", async () => {
328
+ const agent = new BasicAgent({
329
+ model: "openai/gpt-4o",
330
+ maxOutputTokens: 100,
331
+ temperature: 0.5,
332
+ topP: 0.9,
333
+ topK: 50,
334
+ presencePenalty: 0.0,
335
+ frequencyPenalty: 0.0,
336
+ seed: 123,
337
+ maxRetries: 3,
338
+ overridableProperties: [
339
+ "maxOutputTokens",
340
+ "temperature",
341
+ "topP",
342
+ "topK",
343
+ "presencePenalty",
344
+ "frequencyPenalty",
345
+ "seed",
346
+ "maxRetries",
347
+ ],
348
+ });
349
+
350
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
351
+
352
+ const input: RunAgentInput = {
353
+ threadId: "thread1",
354
+ runId: "run1",
355
+ messages: [],
356
+ tools: [],
357
+ context: [],
358
+ state: {},
359
+ forwardedProps: {
360
+ maxOutputTokens: 500,
361
+ temperature: 0.8,
362
+ topP: 0.95,
363
+ topK: 100,
364
+ presencePenalty: 0.5,
365
+ frequencyPenalty: 0.5,
366
+ seed: 456,
367
+ maxRetries: 5,
368
+ },
369
+ };
370
+
371
+ await collectEvents(agent["run"](input));
372
+
373
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
374
+ expect(callArgs.maxOutputTokens).toBe(500);
375
+ expect(callArgs.temperature).toBe(0.8);
376
+ expect(callArgs.topP).toBe(0.95);
377
+ expect(callArgs.topK).toBe(100);
378
+ expect(callArgs.presencePenalty).toBe(0.5);
379
+ expect(callArgs.frequencyPenalty).toBe(0.5);
380
+ expect(callArgs.seed).toBe(456);
381
+ expect(callArgs.maxRetries).toBe(5);
382
+ });
383
+
384
+ it("should ignore non-numeric values for numeric properties", async () => {
385
+ const agent = new BasicAgent({
386
+ model: "openai/gpt-4o",
387
+ temperature: 0.5,
388
+ overridableProperties: ["temperature"],
389
+ });
390
+
391
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
392
+
393
+ const input: RunAgentInput = {
394
+ threadId: "thread1",
395
+ runId: "run1",
396
+ messages: [],
397
+ tools: [],
398
+ context: [],
399
+ state: {},
400
+ forwardedProps: { temperature: "high" as any },
401
+ };
402
+
403
+ await collectEvents(agent["run"](input));
404
+
405
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
406
+ expect(callArgs.temperature).toBe(0.5); // Original value
407
+ });
408
+ });
409
+
410
+ describe("Multiple Property Overrides", () => {
411
+ it("should only override allowed properties", async () => {
412
+ const agent = new BasicAgent({
413
+ model: "openai/gpt-4o",
414
+ temperature: 0.5,
415
+ topP: 0.9,
416
+ maxOutputTokens: 100,
417
+ overridableProperties: ["temperature"], // Only temperature is overridable
418
+ });
419
+
420
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
421
+
422
+ const input: RunAgentInput = {
423
+ threadId: "thread1",
424
+ runId: "run1",
425
+ messages: [],
426
+ tools: [],
427
+ context: [],
428
+ state: {},
429
+ forwardedProps: {
430
+ temperature: 0.9,
431
+ topP: 0.5,
432
+ maxOutputTokens: 500,
433
+ },
434
+ };
435
+
436
+ await collectEvents(agent["run"](input));
437
+
438
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
439
+ expect(callArgs.temperature).toBe(0.9); // Overridden
440
+ expect(callArgs.topP).toBe(0.9); // Original
441
+ expect(callArgs.maxOutputTokens).toBe(100); // Original
442
+ });
443
+
444
+ it("should handle undefined forwardedProps", async () => {
445
+ const agent = new BasicAgent({
446
+ model: "openai/gpt-4o",
447
+ temperature: 0.5,
448
+ overridableProperties: ["temperature"],
449
+ });
450
+
451
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
452
+
453
+ const input: RunAgentInput = {
454
+ threadId: "thread1",
455
+ runId: "run1",
456
+ messages: [],
457
+ tools: [],
458
+ context: [],
459
+ state: {},
460
+ // No forwardedProps
461
+ };
462
+
463
+ await collectEvents(agent["run"](input));
464
+
465
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
466
+ expect(callArgs.temperature).toBe(0.5); // Original value
467
+ });
468
+
469
+ it("should handle non-object forwardedProps", async () => {
470
+ const agent = new BasicAgent({
471
+ model: "openai/gpt-4o",
472
+ temperature: 0.5,
473
+ overridableProperties: ["temperature"],
474
+ });
475
+
476
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
477
+
478
+ const input: RunAgentInput = {
479
+ threadId: "thread1",
480
+ runId: "run1",
481
+ messages: [],
482
+ tools: [],
483
+ context: [],
484
+ state: {},
485
+ forwardedProps: "not an object" as any,
486
+ };
487
+
488
+ await collectEvents(agent["run"](input));
489
+
490
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
491
+ expect(callArgs.temperature).toBe(0.5); // Original value
492
+ });
493
+
494
+ it("should handle undefined property values in forwardedProps", async () => {
495
+ const agent = new BasicAgent({
496
+ model: "openai/gpt-4o",
497
+ temperature: 0.5,
498
+ overridableProperties: ["temperature"],
499
+ });
500
+
501
+ vi.mocked(streamText).mockReturnValue(mockStreamTextResponse([finish()]) as any);
502
+
503
+ const input: RunAgentInput = {
504
+ threadId: "thread1",
505
+ runId: "run1",
506
+ messages: [],
507
+ tools: [],
508
+ context: [],
509
+ state: {},
510
+ forwardedProps: { temperature: undefined },
511
+ };
512
+
513
+ await collectEvents(agent["run"](input));
514
+
515
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
516
+ expect(callArgs.temperature).toBe(0.5); // Original value (undefined is ignored)
517
+ });
518
+ });
519
+
520
+ describe("canOverride method", () => {
521
+ it("should return true for overridable properties", () => {
522
+ const agent = new BasicAgent({
523
+ model: "openai/gpt-4o",
524
+ overridableProperties: ["temperature", "topP"],
525
+ });
526
+
527
+ expect(agent.canOverride("temperature")).toBe(true);
528
+ expect(agent.canOverride("topP")).toBe(true);
529
+ });
530
+
531
+ it("should return false for non-overridable properties", () => {
532
+ const agent = new BasicAgent({
533
+ model: "openai/gpt-4o",
534
+ overridableProperties: ["temperature"],
535
+ });
536
+
537
+ expect(agent.canOverride("topP")).toBe(false);
538
+ expect(agent.canOverride("seed")).toBe(false);
539
+ });
540
+
541
+ it("should return false when overridableProperties is undefined", () => {
542
+ const agent = new BasicAgent({
543
+ model: "openai/gpt-4o",
544
+ });
545
+
546
+ expect(agent.canOverride("temperature")).toBe(false);
547
+ expect(agent.canOverride("topP")).toBe(false);
548
+ });
549
+
550
+ it("should return false when overridableProperties is empty array", () => {
551
+ const agent = new BasicAgent({
552
+ model: "openai/gpt-4o",
553
+ overridableProperties: [],
554
+ });
555
+
556
+ expect(agent.canOverride("temperature")).toBe(false);
557
+ });
558
+ });
559
+ });