@copilotkit/runtime 1.55.2-next.0 → 1.55.2

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 (98) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/agent/converters/aisdk.cjs +215 -0
  3. package/dist/agent/converters/aisdk.cjs.map +1 -0
  4. package/dist/agent/converters/aisdk.d.cts +18 -0
  5. package/dist/agent/converters/aisdk.d.cts.map +1 -0
  6. package/dist/agent/converters/aisdk.d.mts +18 -0
  7. package/dist/agent/converters/aisdk.d.mts.map +1 -0
  8. package/dist/agent/converters/aisdk.mjs +214 -0
  9. package/dist/agent/converters/aisdk.mjs.map +1 -0
  10. package/dist/agent/converters/index.d.mts +3 -0
  11. package/dist/agent/converters/tanstack.cjs +180 -0
  12. package/dist/agent/converters/tanstack.cjs.map +1 -0
  13. package/dist/agent/converters/tanstack.d.cts +68 -0
  14. package/dist/agent/converters/tanstack.d.cts.map +1 -0
  15. package/dist/agent/converters/tanstack.d.mts +68 -0
  16. package/dist/agent/converters/tanstack.d.mts.map +1 -0
  17. package/dist/agent/converters/tanstack.mjs +178 -0
  18. package/dist/agent/converters/tanstack.mjs.map +1 -0
  19. package/dist/agent/index.cjs +111 -17
  20. package/dist/agent/index.cjs.map +1 -1
  21. package/dist/agent/index.d.cts +61 -4
  22. package/dist/agent/index.d.cts.map +1 -1
  23. package/dist/agent/index.d.mts +62 -4
  24. package/dist/agent/index.d.mts.map +1 -1
  25. package/dist/agent/index.mjs +111 -17
  26. package/dist/agent/index.mjs.map +1 -1
  27. package/dist/lib/integrations/nextjs/pages-router.cjs.map +1 -1
  28. package/dist/lib/integrations/nextjs/pages-router.d.cts.map +1 -1
  29. package/dist/lib/integrations/nextjs/pages-router.d.mts.map +1 -1
  30. package/dist/lib/integrations/nextjs/pages-router.mjs.map +1 -1
  31. package/dist/lib/runtime/copilot-runtime.cjs +4 -2
  32. package/dist/lib/runtime/copilot-runtime.cjs.map +1 -1
  33. package/dist/lib/runtime/copilot-runtime.d.cts.map +1 -1
  34. package/dist/lib/runtime/copilot-runtime.d.mts.map +1 -1
  35. package/dist/lib/runtime/copilot-runtime.mjs +4 -2
  36. package/dist/lib/runtime/copilot-runtime.mjs.map +1 -1
  37. package/dist/lib/runtime/mcp-tools-utils.cjs +1 -1
  38. package/dist/lib/runtime/mcp-tools-utils.cjs.map +1 -1
  39. package/dist/lib/runtime/mcp-tools-utils.mjs +1 -1
  40. package/dist/lib/runtime/mcp-tools-utils.mjs.map +1 -1
  41. package/dist/package.cjs +1 -1
  42. package/dist/package.mjs +1 -1
  43. package/dist/service-adapters/anthropic/utils.cjs +1 -1
  44. package/dist/service-adapters/anthropic/utils.cjs.map +1 -1
  45. package/dist/service-adapters/anthropic/utils.mjs +1 -1
  46. package/dist/service-adapters/anthropic/utils.mjs.map +1 -1
  47. package/dist/service-adapters/openai/utils.cjs +1 -1
  48. package/dist/service-adapters/openai/utils.cjs.map +1 -1
  49. package/dist/service-adapters/openai/utils.mjs +1 -1
  50. package/dist/service-adapters/openai/utils.mjs.map +1 -1
  51. package/dist/v2/index.cjs +5 -0
  52. package/dist/v2/index.d.cts +4 -2
  53. package/dist/v2/index.d.mts +4 -2
  54. package/dist/v2/index.mjs +3 -1
  55. package/dist/v2/runtime/core/fetch-handler.cjs +43 -3
  56. package/dist/v2/runtime/core/fetch-handler.cjs.map +1 -1
  57. package/dist/v2/runtime/core/fetch-handler.d.cts.map +1 -1
  58. package/dist/v2/runtime/core/fetch-handler.d.mts.map +1 -1
  59. package/dist/v2/runtime/core/fetch-handler.mjs +43 -3
  60. package/dist/v2/runtime/core/fetch-handler.mjs.map +1 -1
  61. package/dist/v2/runtime/core/fetch-router.cjs +26 -0
  62. package/dist/v2/runtime/core/fetch-router.cjs.map +1 -1
  63. package/dist/v2/runtime/core/fetch-router.mjs +26 -0
  64. package/dist/v2/runtime/core/fetch-router.mjs.map +1 -1
  65. package/dist/v2/runtime/core/hooks.cjs.map +1 -1
  66. package/dist/v2/runtime/core/hooks.d.cts +13 -0
  67. package/dist/v2/runtime/core/hooks.d.cts.map +1 -1
  68. package/dist/v2/runtime/core/hooks.d.mts +13 -0
  69. package/dist/v2/runtime/core/hooks.d.mts.map +1 -1
  70. package/dist/v2/runtime/core/hooks.mjs.map +1 -1
  71. package/dist/v2/runtime/handlers/intelligence/threads.cjs +179 -0
  72. package/dist/v2/runtime/handlers/intelligence/threads.cjs.map +1 -0
  73. package/dist/v2/runtime/handlers/intelligence/threads.mjs +173 -0
  74. package/dist/v2/runtime/handlers/intelligence/threads.mjs.map +1 -0
  75. package/package.json +2 -2
  76. package/src/agent/__tests__/agent-test-helpers.ts +446 -0
  77. package/src/agent/__tests__/agent.test.ts +593 -0
  78. package/src/agent/__tests__/converter-aisdk.test.ts +692 -0
  79. package/src/agent/__tests__/converter-custom.test.ts +319 -0
  80. package/src/agent/__tests__/converter-tanstack-input.test.ts +211 -0
  81. package/src/agent/__tests__/converter-tanstack.test.ts +314 -0
  82. package/src/agent/__tests__/multimodal-tanstack.test.ts +284 -0
  83. package/src/agent/__tests__/test-helpers.ts +12 -8
  84. package/src/agent/converters/aisdk.ts +326 -0
  85. package/src/agent/converters/index.ts +7 -0
  86. package/src/agent/converters/tanstack.ts +286 -0
  87. package/src/agent/index.ts +245 -26
  88. package/src/lib/integrations/nextjs/pages-router.ts +1 -0
  89. package/src/lib/runtime/copilot-runtime.ts +21 -12
  90. package/src/lib/runtime/mcp-tools-utils.ts +1 -1
  91. package/src/service-adapters/anthropic/utils.ts +1 -1
  92. package/src/service-adapters/openai/utils.ts +1 -1
  93. package/src/v2/runtime/__tests__/fetch-router.test.ts +76 -0
  94. package/src/v2/runtime/core/fetch-handler.ts +55 -4
  95. package/src/v2/runtime/core/fetch-router.ts +48 -0
  96. package/src/v2/runtime/core/hooks.ts +6 -1
  97. package/src/v2/runtime/handlers/handle-threads.ts +1 -0
  98. package/src/v2/runtime/handlers/intelligence/threads.ts +28 -0
@@ -0,0 +1,446 @@
1
+ /**
2
+ * Shared test utilities for BuiltInAgent factory-mode tests.
3
+ *
4
+ * Re-exports everything from the existing test-helpers module and adds
5
+ * BuiltInAgent-specific factories, mock stream builders, and assertion helpers.
6
+ */
7
+
8
+ import { EventType, type BaseEvent, type RunAgentInput } from "@ag-ui/client";
9
+ import type { Observable } from "rxjs";
10
+ import { BuiltInAgent } from "../index";
11
+ import type { AgentFactoryContext, BuiltInAgentFactoryConfig } from "../index";
12
+ import type { MockStreamEvent } from "./test-helpers";
13
+
14
+ // Re-export everything from existing test helpers
15
+ export {
16
+ type MockStreamEvent,
17
+ mockStreamTextResponse,
18
+ textStart,
19
+ textDelta,
20
+ toolCallStreamingStart,
21
+ toolCallDelta,
22
+ toolCall,
23
+ toolResult,
24
+ reasoningStart,
25
+ reasoningDelta,
26
+ reasoningEnd,
27
+ finish,
28
+ abort,
29
+ error,
30
+ collectEvents,
31
+ } from "./test-helpers";
32
+
33
+ // Re-export for test files that need to construct agents directly
34
+ export {
35
+ BuiltInAgent,
36
+ type AgentFactoryContext,
37
+ type BuiltInAgentFactoryConfig,
38
+ };
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Default input factory
42
+ // ---------------------------------------------------------------------------
43
+
44
+ /**
45
+ * Returns a valid `RunAgentInput` with sensible defaults.
46
+ * Any field can be overridden via the `overrides` parameter.
47
+ */
48
+ export function createDefaultInput(overrides?: Partial<RunAgentInput>) {
49
+ return {
50
+ threadId: "test-thread",
51
+ runId: "test-run",
52
+ messages: [],
53
+ tools: [],
54
+ context: [],
55
+ state: {},
56
+ forwardedProps: {},
57
+ ...overrides,
58
+ } as RunAgentInput;
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // TanStack mock stream chunk builders
63
+ // ---------------------------------------------------------------------------
64
+
65
+ /** TanStack text content chunk */
66
+ export function tanstackTextChunk(delta: string) {
67
+ return { type: "TEXT_MESSAGE_CONTENT", delta } as const;
68
+ }
69
+
70
+ /** TanStack tool call start chunk */
71
+ export function tanstackToolCallStart(
72
+ toolCallId: string,
73
+ toolCallName: string,
74
+ ) {
75
+ return { type: "TOOL_CALL_START", toolCallId, toolCallName } as const;
76
+ }
77
+
78
+ /** TanStack tool call args chunk */
79
+ export function tanstackToolCallArgs(toolCallId: string, delta: string) {
80
+ return { type: "TOOL_CALL_ARGS", toolCallId, delta } as const;
81
+ }
82
+
83
+ /** TanStack tool call end chunk */
84
+ export function tanstackToolCallEnd(toolCallId: string) {
85
+ return { type: "TOOL_CALL_END", toolCallId } as const;
86
+ }
87
+
88
+ // ---------------------------------------------------------------------------
89
+ // Mock async iterable builders
90
+ // ---------------------------------------------------------------------------
91
+
92
+ /**
93
+ * Creates an `AsyncIterable<unknown>` from an array of TanStack-style chunks.
94
+ */
95
+ export function mockTanStackStream(chunks: Record<string, unknown>[]) {
96
+ return {
97
+ [Symbol.asyncIterator]: async function* () {
98
+ for (const chunk of chunks) {
99
+ yield chunk;
100
+ }
101
+ },
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Creates an `AsyncIterable<BaseEvent>` from an array of AG-UI events.
107
+ */
108
+ export function mockCustomStream(
109
+ events: BaseEvent[],
110
+ ): AsyncIterable<BaseEvent> {
111
+ return {
112
+ [Symbol.asyncIterator]: async function* () {
113
+ for (const event of events) {
114
+ yield event;
115
+ }
116
+ },
117
+ };
118
+ }
119
+
120
+ // ---------------------------------------------------------------------------
121
+ // BuiltInAgent factories
122
+ // ---------------------------------------------------------------------------
123
+
124
+ export type AgentType = "aisdk" | "tanstack" | "custom";
125
+
126
+ /**
127
+ * Creates a BuiltInAgent backed by a mock factory that yields the given stream data.
128
+ *
129
+ * Overloaded for each supported agent type:
130
+ * - `"aisdk"` expects `MockStreamEvent[]` (AI SDK fullStream events)
131
+ * - `"tanstack"` expects `Record<string, unknown>[]` (TanStack chunks)
132
+ * - `"custom"` expects `BaseEvent[]` (AG-UI events directly)
133
+ */
134
+ export function createAgent(
135
+ type: "aisdk",
136
+ streamData: MockStreamEvent[],
137
+ ): BuiltInAgent;
138
+ export function createAgent(
139
+ type: "tanstack",
140
+ streamData: Record<string, unknown>[],
141
+ ): BuiltInAgent;
142
+ export function createAgent(
143
+ type: "custom",
144
+ streamData: BaseEvent[],
145
+ ): BuiltInAgent;
146
+ export function createAgent(
147
+ type: AgentType,
148
+ streamData: MockStreamEvent[] | Record<string, unknown>[] | BaseEvent[],
149
+ ): BuiltInAgent;
150
+ export function createAgent(
151
+ type: AgentType,
152
+ streamData: MockStreamEvent[] | Record<string, unknown>[] | BaseEvent[],
153
+ ): BuiltInAgent {
154
+ switch (type) {
155
+ case "aisdk": {
156
+ // Cast needed: TypeScript's control-flow narrowing doesn't propagate
157
+ // through overload signatures to narrow the union parameter type.
158
+ const events = streamData as MockStreamEvent[];
159
+ return new BuiltInAgent({
160
+ type: "aisdk",
161
+ factory: () => ({
162
+ fullStream: (async function* () {
163
+ for (const event of events) {
164
+ yield event;
165
+ }
166
+ })(),
167
+ }),
168
+ });
169
+ }
170
+ case "tanstack": {
171
+ // Cast needed: same overload-narrowing limitation as above.
172
+ const chunks = streamData as Record<string, unknown>[];
173
+ return new BuiltInAgent({
174
+ type: "tanstack",
175
+ factory: () => mockTanStackStream(chunks),
176
+ });
177
+ }
178
+ case "custom": {
179
+ // Cast needed: same overload-narrowing limitation as above.
180
+ const events = streamData as BaseEvent[];
181
+ return new BuiltInAgent({
182
+ type: "custom",
183
+ factory: () => mockCustomStream(events),
184
+ });
185
+ }
186
+ }
187
+ }
188
+
189
+ // ---------------------------------------------------------------------------
190
+ // Error agent factories
191
+ // ---------------------------------------------------------------------------
192
+
193
+ /**
194
+ * Creates a BuiltInAgent whose factory immediately throws.
195
+ */
196
+ export function createThrowingAgent(
197
+ type: AgentType,
198
+ errorMessage: string,
199
+ ): BuiltInAgent {
200
+ // All three factory signatures accept (ctx) and can throw before returning.
201
+ // Since the factory throws, the return type is irrelevant — TypeScript's
202
+ // `never` return satisfies all three config shapes.
203
+ const thrower = (): never => {
204
+ throw new Error(errorMessage);
205
+ };
206
+
207
+ switch (type) {
208
+ case "aisdk":
209
+ return new BuiltInAgent({ type: "aisdk", factory: thrower });
210
+ case "tanstack":
211
+ return new BuiltInAgent({ type: "tanstack", factory: thrower });
212
+ case "custom":
213
+ return new BuiltInAgent({ type: "custom", factory: thrower });
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Creates a BuiltInAgent that yields one partial event and then throws.
219
+ *
220
+ * - `"aisdk"`: yields `{ type: "text-delta", text: "partial" }` then throws
221
+ * - `"tanstack"`: yields `{ type: "TEXT_MESSAGE_CONTENT", delta: "partial" }` then throws
222
+ * - `"custom"`: yields a `TEXT_MESSAGE_CHUNK` BaseEvent then throws
223
+ */
224
+ export function createMidStreamErrorAgent(
225
+ type: AgentType,
226
+ errorMessage: string,
227
+ ): BuiltInAgent {
228
+ switch (type) {
229
+ case "aisdk": {
230
+ return new BuiltInAgent({
231
+ type: "aisdk",
232
+ factory: () => ({
233
+ fullStream: (async function* () {
234
+ yield { type: "text-delta", text: "partial" };
235
+ throw new Error(errorMessage);
236
+ })(),
237
+ }),
238
+ });
239
+ }
240
+ case "tanstack": {
241
+ return new BuiltInAgent({
242
+ type: "tanstack",
243
+ factory: () => ({
244
+ [Symbol.asyncIterator]: async function* () {
245
+ yield {
246
+ type: "TEXT_MESSAGE_CONTENT",
247
+ delta: "partial",
248
+ };
249
+ throw new Error(errorMessage);
250
+ },
251
+ }),
252
+ });
253
+ }
254
+ case "custom": {
255
+ return new BuiltInAgent({
256
+ type: "custom",
257
+ factory: () => ({
258
+ [Symbol.asyncIterator]: async function* () {
259
+ yield {
260
+ type: EventType.TEXT_MESSAGE_CHUNK,
261
+ role: "assistant",
262
+ delta: "partial",
263
+ } as const as BaseEvent;
264
+ throw new Error(errorMessage);
265
+ },
266
+ }),
267
+ });
268
+ }
269
+ }
270
+ }
271
+
272
+ // ---------------------------------------------------------------------------
273
+ // Event collection utilities
274
+ // ---------------------------------------------------------------------------
275
+
276
+ /**
277
+ * Result of collecting events from an observable that may error.
278
+ */
279
+ export interface CollectedEventsResult {
280
+ events: BaseEvent[];
281
+ /** Whether the observable completed via error (true) or normal completion (false) */
282
+ errored: boolean;
283
+ /** Whether the safety timeout fired (indicates a hung observable) */
284
+ timedOut: boolean;
285
+ }
286
+
287
+ /**
288
+ * Like `collectEvents` but resolves (instead of rejecting) when the
289
+ * observable errors. Returns the events collected up to and including
290
+ * the error point, along with whether it errored or completed normally.
291
+ */
292
+ export async function collectEventsIncludingErrors(
293
+ observable: Observable<BaseEvent>,
294
+ ): Promise<CollectedEventsResult> {
295
+ return new Promise((resolve) => {
296
+ const events: BaseEvent[] = [];
297
+ const timeoutId = setTimeout(() => {
298
+ subscription.unsubscribe();
299
+ resolve({ events, errored: false, timedOut: true });
300
+ }, 5000);
301
+ const subscription = observable.subscribe({
302
+ next: (event) => events.push(event),
303
+ error: () => {
304
+ clearTimeout(timeoutId);
305
+ resolve({ events, errored: true, timedOut: false });
306
+ },
307
+ complete: () => {
308
+ clearTimeout(timeoutId);
309
+ resolve({ events, errored: false, timedOut: false });
310
+ },
311
+ });
312
+ });
313
+ }
314
+
315
+ // ---------------------------------------------------------------------------
316
+ // Typed event field accessors (avoids `as any` casts in tests)
317
+ // ---------------------------------------------------------------------------
318
+
319
+ /**
320
+ * Reads a field from a BaseEvent. AG-UI's BaseEvent uses `.passthrough()` so
321
+ * extra fields exist at runtime but aren't in the static type. This helper
322
+ * provides typed access without casts.
323
+ */
324
+ export function eventField<T = unknown>(event: BaseEvent, field: string): T {
325
+ return (event as Record<string, unknown>)[field] as T;
326
+ }
327
+
328
+ // ---------------------------------------------------------------------------
329
+ // Assertion helpers
330
+ // ---------------------------------------------------------------------------
331
+
332
+ /**
333
+ * Asserts that the events are wrapped with RUN_STARTED as the first event
334
+ * and RUN_FINISHED as the last event. Optionally checks threadId and runId.
335
+ */
336
+ export function expectLifecycleWrapped(
337
+ events: BaseEvent[],
338
+ threadId?: string,
339
+ runId?: string,
340
+ ): void {
341
+ if (events.length < 2) {
342
+ throw new Error(
343
+ `Expected at least 2 events (RUN_STARTED + RUN_FINISHED), got ${events.length}`,
344
+ );
345
+ }
346
+
347
+ const first = events[0];
348
+ const last = events[events.length - 1];
349
+
350
+ if (first.type !== EventType.RUN_STARTED) {
351
+ throw new Error(
352
+ `Expected first event to be RUN_STARTED, got ${first.type}`,
353
+ );
354
+ }
355
+
356
+ if (last.type !== EventType.RUN_FINISHED) {
357
+ throw new Error(`Expected last event to be RUN_FINISHED, got ${last.type}`);
358
+ }
359
+
360
+ if (threadId !== undefined) {
361
+ if (eventField<string>(first, "threadId") !== threadId) {
362
+ throw new Error(
363
+ `Expected RUN_STARTED threadId to be "${threadId}", got "${eventField(first, "threadId")}"`,
364
+ );
365
+ }
366
+ if (eventField<string>(last, "threadId") !== threadId) {
367
+ throw new Error(
368
+ `Expected RUN_FINISHED threadId to be "${threadId}", got "${eventField(last, "threadId")}"`,
369
+ );
370
+ }
371
+ }
372
+
373
+ if (runId !== undefined) {
374
+ if (eventField<string>(first, "runId") !== runId) {
375
+ throw new Error(
376
+ `Expected RUN_STARTED runId to be "${runId}", got "${eventField(first, "runId")}"`,
377
+ );
378
+ }
379
+ if (eventField<string>(last, "runId") !== runId) {
380
+ throw new Error(
381
+ `Expected RUN_FINISHED runId to be "${runId}", got "${eventField(last, "runId")}"`,
382
+ );
383
+ }
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Asserts that the event types match the expected sequence exactly.
389
+ */
390
+ export function expectEventSequence(
391
+ events: BaseEvent[],
392
+ expectedTypes: EventType[],
393
+ ): void {
394
+ const actualTypes = events.map((e) => e.type);
395
+
396
+ if (actualTypes.length !== expectedTypes.length) {
397
+ throw new Error(
398
+ `Event count mismatch: expected ${expectedTypes.length}, got ${actualTypes.length}.\n` +
399
+ `Expected: [${expectedTypes.join(", ")}]\n` +
400
+ `Actual: [${actualTypes.join(", ")}]`,
401
+ );
402
+ }
403
+
404
+ for (let i = 0; i < expectedTypes.length; i++) {
405
+ if (actualTypes[i] !== expectedTypes[i]) {
406
+ throw new Error(
407
+ `Event type mismatch at index ${i}: expected ${expectedTypes[i]}, got ${actualTypes[i]}.\n` +
408
+ `Expected: [${expectedTypes.join(", ")}]\n` +
409
+ `Actual: [${actualTypes.join(", ")}]`,
410
+ );
411
+ }
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Asserts that no content events appear after the specified terminal event type.
417
+ *
418
+ * "Content events" are everything except RUN_STARTED, RUN_FINISHED, and RUN_ERROR.
419
+ */
420
+ export function expectNoEventAfter(
421
+ events: BaseEvent[],
422
+ terminalType: EventType,
423
+ ): void {
424
+ const terminalIndex = events.findIndex((e) => e.type === terminalType);
425
+ if (terminalIndex === -1) {
426
+ throw new Error(`Terminal event type ${terminalType} not found in events`);
427
+ }
428
+
429
+ const lifecycleTypes = new Set([
430
+ EventType.RUN_STARTED,
431
+ EventType.RUN_FINISHED,
432
+ EventType.RUN_ERROR,
433
+ ]);
434
+
435
+ const eventsAfter = events.slice(terminalIndex + 1);
436
+ const contentEventsAfter = eventsAfter.filter(
437
+ (e) => !lifecycleTypes.has(e.type),
438
+ );
439
+
440
+ if (contentEventsAfter.length > 0) {
441
+ throw new Error(
442
+ `Found ${contentEventsAfter.length} content event(s) after ${terminalType}: ` +
443
+ `[${contentEventsAfter.map((e) => e.type).join(", ")}]`,
444
+ );
445
+ }
446
+ }