@copilotkit/runtime 1.10.7-next.0 → 1.50.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/CHANGELOG.md +0 -6
  2. package/dist/index.d.ts +1655 -27
  3. package/dist/index.js +2172 -5049
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +5441 -99
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/v2/index.d.ts +1 -0
  8. package/dist/v2/index.js +15 -0
  9. package/dist/v2/index.js.map +1 -0
  10. package/dist/v2/index.mjs +4 -0
  11. package/dist/v2/index.mjs.map +1 -0
  12. package/package.json +17 -5
  13. package/src/graphql/message-conversion/agui-to-gql.test.ts +1263 -0
  14. package/src/graphql/message-conversion/agui-to-gql.ts +333 -0
  15. package/src/graphql/message-conversion/gql-to-agui.test.ts +1578 -0
  16. package/src/graphql/message-conversion/gql-to-agui.ts +278 -0
  17. package/src/graphql/message-conversion/index.ts +2 -0
  18. package/src/graphql/message-conversion/roundtrip-conversion.test.ts +526 -0
  19. package/src/graphql/resolvers/copilot.resolver.ts +3 -48
  20. package/src/graphql/resolvers/state.resolver.ts +3 -2
  21. package/src/graphql/types/converted/index.ts +32 -6
  22. package/src/graphql/types/enums.ts +2 -2
  23. package/src/graphql/types/message-status.type.ts +3 -1
  24. package/src/lib/index.ts +1 -1
  25. package/src/lib/integrations/nextjs/app-router.ts +10 -11
  26. package/src/lib/integrations/nextjs/pages-router.ts +4 -11
  27. package/src/lib/integrations/node-http/index.ts +64 -5
  28. package/src/lib/integrations/shared.ts +1 -1
  29. package/src/lib/observability.ts +87 -0
  30. package/src/lib/runtime/{langgraph/langgraph-agent.ts → agent-integrations/langgraph.agent.ts} +5 -0
  31. package/src/lib/runtime/copilot-runtime.ts +346 -1333
  32. package/src/lib/runtime/types.ts +49 -0
  33. package/src/lib/runtime/utils.ts +87 -0
  34. package/src/lib/telemetry-client.ts +6 -5
  35. package/src/service-adapters/anthropic/anthropic-adapter.ts +5 -1
  36. package/src/service-adapters/bedrock/bedrock-adapter.ts +6 -1
  37. package/src/service-adapters/empty/empty-adapter.ts +3 -0
  38. package/src/service-adapters/events.ts +0 -254
  39. package/src/service-adapters/experimental/ollama/ollama-adapter.ts +5 -1
  40. package/src/service-adapters/google/google-genai-adapter.ts +7 -1
  41. package/src/service-adapters/groq/groq-adapter.ts +5 -1
  42. package/src/service-adapters/langchain/langchain-adapter.ts +3 -0
  43. package/src/service-adapters/openai/openai-adapter.ts +5 -1
  44. package/src/service-adapters/openai/openai-assistant-adapter.ts +4 -0
  45. package/src/service-adapters/service-adapter.ts +3 -0
  46. package/src/service-adapters/unify/unify-adapter.ts +6 -1
  47. package/src/v2/index.ts +2 -0
  48. package/tsup.config.ts +2 -1
  49. package/dist/chunk-27JKTS6P.mjs +0 -1704
  50. package/dist/chunk-27JKTS6P.mjs.map +0 -1
  51. package/dist/chunk-2OZAGFV3.mjs +0 -43
  52. package/dist/chunk-2OZAGFV3.mjs.map +0 -1
  53. package/dist/chunk-5BW5IBTZ.mjs +0 -80
  54. package/dist/chunk-5BW5IBTZ.mjs.map +0 -1
  55. package/dist/chunk-AMUJQ6IR.mjs +0 -50
  56. package/dist/chunk-AMUJQ6IR.mjs.map +0 -1
  57. package/dist/chunk-BMIYSM5W.mjs +0 -25
  58. package/dist/chunk-BMIYSM5W.mjs.map +0 -1
  59. package/dist/chunk-FDTCG47E.mjs +0 -25
  60. package/dist/chunk-FDTCG47E.mjs.map +0 -1
  61. package/dist/chunk-FHD4JECV.mjs +0 -33
  62. package/dist/chunk-FHD4JECV.mjs.map +0 -1
  63. package/dist/chunk-LRCKLBMO.mjs +0 -6020
  64. package/dist/chunk-LRCKLBMO.mjs.map +0 -1
  65. package/dist/chunk-R7RMYEPZ.mjs +0 -175
  66. package/dist/chunk-R7RMYEPZ.mjs.map +0 -1
  67. package/dist/chunk-SHBDMA63.mjs +0 -141
  68. package/dist/chunk-SHBDMA63.mjs.map +0 -1
  69. package/dist/chunk-XWBDEXDA.mjs +0 -153
  70. package/dist/chunk-XWBDEXDA.mjs.map +0 -1
  71. package/dist/graphql/types/base/index.d.ts +0 -6
  72. package/dist/graphql/types/base/index.js +0 -63
  73. package/dist/graphql/types/base/index.js.map +0 -1
  74. package/dist/graphql/types/base/index.mjs +0 -8
  75. package/dist/graphql/types/base/index.mjs.map +0 -1
  76. package/dist/graphql/types/converted/index.d.ts +0 -2
  77. package/dist/graphql/types/converted/index.js +0 -200
  78. package/dist/graphql/types/converted/index.js.map +0 -1
  79. package/dist/graphql/types/converted/index.mjs +0 -19
  80. package/dist/graphql/types/converted/index.mjs.map +0 -1
  81. package/dist/groq-adapter-c8aec5c5.d.ts +0 -321
  82. package/dist/index-96b330da.d.ts +0 -119
  83. package/dist/langserve-0c6100e3.d.ts +0 -257
  84. package/dist/lib/cloud/index.d.ts +0 -6
  85. package/dist/lib/cloud/index.js +0 -18
  86. package/dist/lib/cloud/index.js.map +0 -1
  87. package/dist/lib/cloud/index.mjs +0 -1
  88. package/dist/lib/cloud/index.mjs.map +0 -1
  89. package/dist/lib/index.d.ts +0 -212
  90. package/dist/lib/index.js +0 -7843
  91. package/dist/lib/index.js.map +0 -1
  92. package/dist/lib/index.mjs +0 -76
  93. package/dist/lib/index.mjs.map +0 -1
  94. package/dist/lib/integrations/index.d.ts +0 -34
  95. package/dist/lib/integrations/index.js +0 -3052
  96. package/dist/lib/integrations/index.js.map +0 -1
  97. package/dist/lib/integrations/index.mjs +0 -37
  98. package/dist/lib/integrations/index.mjs.map +0 -1
  99. package/dist/lib/integrations/nest/index.d.ts +0 -15
  100. package/dist/lib/integrations/nest/index.js +0 -2959
  101. package/dist/lib/integrations/nest/index.js.map +0 -1
  102. package/dist/lib/integrations/nest/index.mjs +0 -14
  103. package/dist/lib/integrations/nest/index.mjs.map +0 -1
  104. package/dist/lib/integrations/node-express/index.d.ts +0 -15
  105. package/dist/lib/integrations/node-express/index.js +0 -2959
  106. package/dist/lib/integrations/node-express/index.js.map +0 -1
  107. package/dist/lib/integrations/node-express/index.mjs +0 -14
  108. package/dist/lib/integrations/node-express/index.mjs.map +0 -1
  109. package/dist/lib/integrations/node-http/index.d.ts +0 -15
  110. package/dist/lib/integrations/node-http/index.js +0 -2945
  111. package/dist/lib/integrations/node-http/index.js.map +0 -1
  112. package/dist/lib/integrations/node-http/index.mjs +0 -13
  113. package/dist/lib/integrations/node-http/index.mjs.map +0 -1
  114. package/dist/service-adapters/index.d.ts +0 -162
  115. package/dist/service-adapters/index.js +0 -1787
  116. package/dist/service-adapters/index.js.map +0 -1
  117. package/dist/service-adapters/index.mjs +0 -34
  118. package/dist/service-adapters/index.mjs.map +0 -1
  119. package/dist/service-adapters/shared/index.d.ts +0 -9
  120. package/dist/service-adapters/shared/index.js +0 -72
  121. package/dist/service-adapters/shared/index.js.map +0 -1
  122. package/dist/service-adapters/shared/index.mjs +0 -8
  123. package/dist/service-adapters/shared/index.mjs.map +0 -1
  124. package/dist/shared-0a7346ce.d.ts +0 -466
  125. package/dist/utils/index.d.ts +0 -65
  126. package/dist/utils/index.js +0 -175
  127. package/dist/utils/index.js.map +0 -1
  128. package/dist/utils/index.mjs +0 -12
  129. package/dist/utils/index.mjs.map +0 -1
  130. package/src/lib/runtime/__tests__/remote-action-constructors.test.ts +0 -246
  131. package/src/lib/runtime/agui-action.ts +0 -180
  132. package/src/lib/runtime/remote-action-constructors.ts +0 -331
  133. package/src/lib/runtime/remote-actions.ts +0 -217
  134. package/src/lib/runtime/remote-lg-action.ts +0 -1006
@@ -14,57 +14,44 @@
14
14
 
15
15
  import {
16
16
  Action,
17
- actionParametersToJsonSchema,
18
- Parameter,
19
- ResolvedCopilotKitError,
20
- CopilotKitApiDiscoveryError,
21
- randomId,
22
- CopilotKitError,
23
- CopilotKitAgentDiscoveryError,
24
- CopilotKitMisuseError,
25
- CopilotKitErrorCode,
26
- CopilotKitLowLevelError,
27
17
  CopilotErrorHandler,
28
- CopilotErrorEvent,
29
- CopilotRequestContext,
30
- ensureStructuredError,
18
+ CopilotKitMisuseError,
19
+ MaybePromise,
20
+ NonEmptyRecord,
21
+ Parameter,
22
+ readBody,
23
+ getZodParameters,
24
+ PartialBy,
31
25
  } from "@copilotkit/shared";
26
+ import { type RunAgentInput } from "@ag-ui/core";
27
+ import { aguiToGQL } from "../../graphql/message-conversion/agui-to-gql";
28
+ import { CopilotServiceAdapter, RemoteChainParameters } from "../../service-adapters";
32
29
  import {
33
- CopilotServiceAdapter,
34
- EmptyAdapter,
35
- RemoteChain,
36
- RemoteChainParameters,
37
- } from "../../service-adapters";
30
+ CopilotRuntime as CopilotRuntimeVNext,
31
+ CopilotRuntimeOptions,
32
+ CopilotRuntimeOptions as CopilotRuntimeOptionsVNext,
33
+ InMemoryAgentRunner as InMemoryAgentRunnerVNext,
34
+ } from "@copilotkitnext/runtime";
38
35
 
39
36
  import { MessageInput } from "../../graphql/inputs/message.input";
40
37
  import { ActionInput } from "../../graphql/inputs/action.input";
41
- import { RuntimeEventSource, RuntimeEventTypes } from "../../service-adapters/events";
42
- import { convertGqlInputToMessages } from "../../service-adapters/conversion";
38
+ import { RuntimeEventSource } from "../../service-adapters/events";
43
39
  import { Message } from "../../graphql/types/converted";
44
40
  import { ForwardedParametersInput } from "../../graphql/inputs/forwarded-parameters.input";
45
41
 
46
42
  import {
47
- isRemoteAgentAction,
48
43
  EndpointType,
49
- setupRemoteActions,
50
44
  EndpointDefinition,
51
45
  CopilotKitEndpoint,
52
46
  LangGraphPlatformEndpoint,
53
- } from "./remote-actions";
47
+ } from "./types";
54
48
 
55
49
  import { GraphQLContext } from "../integrations/shared";
56
50
  import { AgentSessionInput } from "../../graphql/inputs/agent-session.input";
57
- import { from } from "rxjs";
58
51
  import { AgentStateInput } from "../../graphql/inputs/agent-state.input";
59
- import { ActionInputAvailability } from "../../graphql/types/enums";
60
- import { createHeaders } from "./remote-action-constructors";
61
- import { fetchWithRetry } from "./retry-utils";
62
52
  import { Agent } from "../../graphql/types/agents-response.type";
63
53
  import { ExtensionsInput } from "../../graphql/inputs/extensions.input";
64
54
  import { ExtensionsResponse } from "../../graphql/types/extensions-response.type";
65
- import { LoadAgentStateResponse } from "../../graphql/types/load-agent-state-response.type";
66
- import { Client as LangGraphClient } from "@langchain/langgraph-sdk";
67
- import { langchainMessagesToCopilotKit } from "./remote-lg-action";
68
55
  import { MetaEventInput } from "../../graphql/inputs/meta-event.input";
69
56
  import {
70
57
  CopilotObservabilityConfig,
@@ -73,51 +60,20 @@ import {
73
60
  LLMErrorData,
74
61
  } from "../observability";
75
62
  import { AbstractAgent } from "@ag-ui/client";
76
- import { MessageRole } from "../../graphql/types/enums";
77
63
 
78
64
  // +++ MCP Imports +++
79
65
  import {
80
66
  MCPClient,
81
67
  MCPEndpointConfig,
82
68
  MCPTool,
69
+ extractParametersFromSchema,
83
70
  convertMCPToolsToActions,
84
71
  generateMcpToolInstructions,
85
72
  } from "./mcp-tools-utils";
86
- import { LangGraphAgent } from "./langgraph/langgraph-agent";
73
+ import { LangGraphAgent } from "./agent-integrations/langgraph.agent";
74
+ import { BasicAgent, BasicAgentConfiguration } from "@copilotkitnext/agent";
87
75
  // Define the function type alias here or import if defined elsewhere
88
76
  type CreateMCPClientFunction = (config: MCPEndpointConfig) => Promise<MCPClient>;
89
- // --- MCP Imports ---
90
-
91
- import { generateHelpfulErrorMessage } from "../streaming";
92
- import { CopilotContextInput } from "../../graphql/inputs/copilot-context.input";
93
- import { RemoteAgentAction } from "./agui-action";
94
-
95
- export interface CopilotRuntimeRequest {
96
- serviceAdapter: CopilotServiceAdapter;
97
- messages: MessageInput[];
98
- actions: ActionInput[];
99
- agentSession?: AgentSessionInput;
100
- agentStates?: AgentStateInput[];
101
- outputMessagesPromise: Promise<Message[]>;
102
- threadId?: string;
103
- runId?: string;
104
- publicApiKey?: string;
105
- graphqlContext: GraphQLContext;
106
- forwardedParameters?: ForwardedParametersInput;
107
- url?: string;
108
- extensions?: ExtensionsInput;
109
- metaEvents?: MetaEventInput[];
110
- context?: CopilotContextInput[];
111
- }
112
-
113
- interface CopilotRuntimeResponse {
114
- threadId: string;
115
- runId?: string;
116
- eventSource: RuntimeEventSource;
117
- serverSideActions: Action<any>[];
118
- actionInputsWithoutAgents: ActionInput[];
119
- extensions?: ExtensionsResponse;
120
- }
121
77
 
122
78
  type ActionsConfiguration<T extends Parameter[] | [] = []> =
123
79
  | Action<T>[]
@@ -157,17 +113,23 @@ interface Middleware {
157
113
  /**
158
114
  * A function that is called before the request is processed.
159
115
  */
116
+ /**
117
+ * @deprecated This middleware hook is deprecated and will be removed in a future version.
118
+ * Use updated middleware integration methods in CopilotRuntimeVNext instead.
119
+ */
160
120
  onBeforeRequest?: OnBeforeRequestHandler;
161
121
 
162
122
  /**
163
123
  * A function that is called after the request is processed.
164
124
  */
125
+ /**
126
+ * @deprecated This middleware hook is deprecated and will be removed in a future version.
127
+ * Use updated middleware integration methods in CopilotRuntimeVNext instead.
128
+ */
165
129
  onAfterRequest?: OnAfterRequestHandler;
166
130
  }
167
131
 
168
- type AgentWithEndpoint = Agent & { endpoint: EndpointDefinition };
169
-
170
- export interface CopilotRuntimeConstructorParams<T extends Parameter[] | [] = []> {
132
+ export interface CopilotRuntimeConstructorParams_BASE<T extends Parameter[] | [] = []> {
171
133
  /**
172
134
  * Middleware to be used by the runtime.
173
135
  *
@@ -190,6 +152,10 @@ export interface CopilotRuntimeConstructorParams<T extends Parameter[] | [] = []
190
152
  * }) => void | Promise<void>;
191
153
  * ```
192
154
  */
155
+ /**
156
+ * @deprecated This middleware hook is deprecated and will be removed in a future version.
157
+ * Use updated middleware integration methods in CopilotRuntimeVNext instead.
158
+ */
193
159
  middleware?: Middleware;
194
160
 
195
161
  /*
@@ -312,1342 +278,389 @@ export interface CopilotRuntimeConstructorParams<T extends Parameter[] | [] = []
312
278
  onError?: CopilotErrorHandler;
313
279
 
314
280
  onStopGeneration?: OnStopGenerationHandler;
315
- }
316
-
317
- export class CopilotRuntime<const T extends Parameter[] | [] = []> {
318
- public actions: ActionsConfiguration<T>;
319
- public agents: Record<string, AbstractAgent>;
320
- public remoteEndpointDefinitions: EndpointDefinition[];
321
- private langserve: Promise<Action<any>>[] = [];
322
- private onBeforeRequest?: OnBeforeRequestHandler;
323
- private onAfterRequest?: OnAfterRequestHandler;
324
- private onStopGeneration?: OnStopGenerationHandler;
325
- private delegateAgentProcessingToServiceAdapter: boolean;
326
- private observability?: CopilotObservabilityConfig;
327
- private availableAgents: Pick<AgentWithEndpoint, "name" | "id">[];
328
- private onError?: CopilotErrorHandler;
329
- private hasWarnedAboutError = false;
330
-
331
- // +++ MCP Properties +++
332
- private readonly mcpServersConfig?: MCPEndpointConfig[];
333
- private mcpActionCache = new Map<string, Action<any>[]>();
334
- // --- MCP Properties ---
335
-
336
- // +++ MCP Client Factory +++
337
- private readonly createMCPClientImpl?: CreateMCPClientFunction;
338
- // --- MCP Client Factory ---
339
-
340
- constructor(params?: CopilotRuntimeConstructorParams<T>) {
341
- if (
342
- params?.remoteEndpoints &&
343
- params?.remoteEndpoints.some((e) => e.type === EndpointType.LangGraphPlatform)
344
- ) {
345
- throw new CopilotKitMisuseError({
346
- message:
347
- "LangGraph Platform remote endpoints are deprecated in favor of the `agents` property. Refer to https://docs.copilotkit.ai/langgraph for more information.",
348
- });
349
- }
350
-
351
- if (
352
- params?.actions &&
353
- params?.remoteEndpoints &&
354
- params?.remoteEndpoints.some((e) => e.type === EndpointType.LangGraphPlatform)
355
- ) {
356
- console.warn("Actions set in runtime instance will not be available for the agent");
357
- console.warn(
358
- `LangGraph Platform remote endpoints are deprecated in favor of the "agents" property`,
359
- );
360
- }
361
281
 
362
- // TODO: finalize
363
- // if (
364
- // params?.agents &&
365
- // Object.values(params.agents).some((agent) => {
366
- // return agent instanceof AguiLangGraphAgent && !(agent instanceof LangGraphAgent);
367
- // })
368
- // ) {
369
- // console.warn('LangGraph Agent class should be imported from @copilotkit/runtime. ')
370
- // }
371
-
372
- this.actions = params?.actions || [];
373
- this.availableAgents = [];
374
-
375
- for (const chain of params?.langserve || []) {
376
- const remoteChain = new RemoteChain(chain);
377
- this.langserve.push(remoteChain.toAction());
378
- }
282
+ // /** Optional transcription service for audio processing. */
283
+ // transcriptionService?: CopilotRuntimeOptionsVNext["transcriptionService"];
284
+ // /** Optional *before* middleware – callback function or webhook URL. */
285
+ // beforeRequestMiddleware?: CopilotRuntimeOptionsVNext["beforeRequestMiddleware"];
286
+ // /** Optional *after* middleware callback function or webhook URL. */
287
+ // afterRequestMiddleware?: CopilotRuntimeOptionsVNext["afterRequestMiddleware"];
288
+ }
379
289
 
380
- this.remoteEndpointDefinitions = params?.remoteEndpoints ?? params?.remoteActions ?? [];
290
+ type BeforeRequestMiddleware = CopilotRuntimeOptionsVNext["beforeRequestMiddleware"];
291
+ type AfterRequestMiddleware = CopilotRuntimeOptionsVNext["afterRequestMiddleware"];
292
+ type BeforeRequestMiddlewareFn = Exclude<BeforeRequestMiddleware, string>;
293
+ type BeforeRequestMiddlewareFnParameters = Parameters<BeforeRequestMiddlewareFn>;
294
+ type BeforeRequestMiddlewareFnResult = ReturnType<BeforeRequestMiddlewareFn>;
295
+ type AfterRequestMiddlewareFn = Exclude<AfterRequestMiddleware, string>;
296
+ type AfterRequestMiddlewareFnParameters = Parameters<AfterRequestMiddlewareFn>;
297
+
298
+ interface CopilotRuntimeConstructorParams<T extends Parameter[] | [] = []>
299
+ extends Omit<CopilotRuntimeConstructorParams_BASE<T>, "agents">,
300
+ Omit<CopilotRuntimeOptionsVNext, "agents" | "transcriptionService"> {
301
+ /**
302
+ * TODO: un-omit `transcriptionService` above once it's supported
303
+ *
304
+ * This satisfies...
305
+ * – the optional constraint in `CopilotRuntimeConstructorParams_BASE`
306
+ * – the `MaybePromise<NonEmptyRecord<T>>` constraint in `CopilotRuntimeOptionsVNext`
307
+ * – the `Record<string, AbstractAgent>` constraint in `both
308
+ */
309
+ agents?: MaybePromise<NonEmptyRecord<Record<string, AbstractAgent>>>;
310
+ }
381
311
 
382
- this.onBeforeRequest = params?.middleware?.onBeforeRequest;
383
- this.onAfterRequest = params?.middleware?.onAfterRequest;
384
- this.onStopGeneration = params?.onStopGeneration;
385
- this.delegateAgentProcessingToServiceAdapter =
386
- params?.delegateAgentProcessingToServiceAdapter || false;
312
+ /**
313
+ * Central runtime object passed to all request handlers.
314
+ */
315
+ export class CopilotRuntime {
316
+ params?: CopilotRuntimeConstructorParams;
317
+ private observability?: CopilotObservabilityConfig;
318
+ // Cache MCP tools per endpoint to avoid re-fetching repeatedly
319
+ private mcpToolsCache: Map<string, BasicAgentConfiguration["tools"]> = new Map();
320
+ private runtimeArgs: CopilotRuntimeOptions;
321
+ private _instance: CopilotRuntimeVNext;
322
+
323
+ constructor(
324
+ params?: CopilotRuntimeConstructorParams & PartialBy<CopilotRuntimeOptions, "agents">,
325
+ ) {
326
+ const agents = params?.agents ?? {};
327
+ this.runtimeArgs = {
328
+ agents: { ...this.assignEndpointsToAgents(params?.remoteEndpoints ?? []), ...agents },
329
+ runner: params?.runner ?? new InMemoryAgentRunnerVNext(),
330
+ // TODO: add support for transcriptionService from CopilotRuntimeOptionsVNext once it is ready
331
+ // transcriptionService: params?.transcriptionService,
332
+
333
+ beforeRequestMiddleware: this.createOnBeforeRequestHandler(params).bind(this),
334
+ afterRequestMiddleware: this.createOnAfterRequestHandler(params).bind(this),
335
+ };
336
+ this.params = params;
387
337
  this.observability = params?.observability_c;
388
- this.agents = params?.agents ?? {};
389
- this.onError = params?.onError;
390
- // +++ MCP Initialization +++
391
- this.mcpServersConfig = params?.mcpServers;
392
- this.createMCPClientImpl = params?.createMCPClient;
393
-
394
- // Validate: If mcpServers are provided, createMCPClient must also be provided
395
- if (this.mcpServersConfig && this.mcpServersConfig.length > 0 && !this.createMCPClientImpl) {
396
- throw new CopilotKitMisuseError({
397
- message:
398
- "MCP Integration Error: `mcpServers` were provided, but the `createMCPClient` function was not passed to the CopilotRuntime constructor. " +
399
- "Please provide an implementation for `createMCPClient`.",
400
- });
401
- }
402
-
403
- // Warning if actions are defined alongside LangGraph platform (potentially MCP too?)
404
- if (
405
- params?.actions &&
406
- (params?.remoteEndpoints?.some((e) => e.type === EndpointType.LangGraphPlatform) ||
407
- this.mcpServersConfig?.length)
408
- ) {
409
- console.warn(
410
- "Local 'actions' defined in CopilotRuntime might not be available to remote agents (LangGraph, MCP). Consider defining actions closer to the agent implementation if needed.",
411
- );
412
- }
413
338
  }
414
339
 
415
- // +++ MCP Instruction Injection Method +++
416
- private injectMCPToolInstructions(
417
- messages: MessageInput[],
418
- currentActions: Action<any>[],
419
- ): MessageInput[] {
420
- // Filter the *passed-in* actions for MCP tools
421
- const mcpActionsForRequest = currentActions.filter((action) => (action as any)._isMCPTool);
422
-
423
- if (!mcpActionsForRequest || mcpActionsForRequest.length === 0) {
424
- return messages; // No MCP tools for this specific request
340
+ get instance() {
341
+ if (!this._instance) {
342
+ this._instance = new CopilotRuntimeVNext(this.runtimeArgs);
425
343
  }
426
344
 
427
- // Create a map to deduplicate tools by name (keeping the last one if duplicates exist)
428
- const uniqueMcpTools = new Map<string, Action<any>>();
429
-
430
- // Add all MCP tools to the map with their names as keys
431
- mcpActionsForRequest.forEach((action) => {
432
- uniqueMcpTools.set(action.name, action);
433
- });
434
-
435
- // Format instructions from the unique tools map
436
- // Convert Action objects to MCPTool format for the instruction generator
437
- const toolsMap: Record<string, MCPTool> = {};
438
- Array.from(uniqueMcpTools.values()).forEach((action) => {
439
- toolsMap[action.name] = {
440
- description: action.description || "",
441
- schema: action.parameters
442
- ? {
443
- parameters: {
444
- properties: action.parameters.reduce(
445
- (acc, p) => ({
446
- ...acc,
447
- [p.name]: { type: p.type, description: p.description },
448
- }),
449
- {},
450
- ),
451
- required: action.parameters.filter((p) => p.required).map((p) => p.name),
452
- },
453
- }
454
- : {},
455
- execute: async () => ({}), // Placeholder, not used for instructions
456
- };
457
- });
458
-
459
- // Generate instructions using the exported helper
460
- const mcpToolInstructions = generateMcpToolInstructions(toolsMap);
461
-
462
- if (!mcpToolInstructions) {
463
- return messages; // No MCP tools to describe
464
- }
465
-
466
- const instructions =
467
- mcpToolInstructions + "\nUse them when appropriate to fulfill the user's request.";
468
-
469
- const systemMessageIndex = messages.findIndex((msg) => msg.textMessage?.role === "system");
470
-
471
- const newMessages = [...messages]; // Create a mutable copy
472
-
473
- if (systemMessageIndex !== -1) {
474
- const existingMsg = newMessages[systemMessageIndex];
475
- if (existingMsg.textMessage) {
476
- existingMsg.textMessage.content =
477
- (existingMsg.textMessage.content ? existingMsg.textMessage.content + "\n\n" : "") +
478
- instructions;
479
- }
480
- } else {
481
- newMessages.unshift({
482
- id: randomId(),
483
- createdAt: new Date(),
484
- textMessage: {
485
- role: MessageRole.system,
486
- content: instructions,
487
- },
488
- actionExecutionMessage: undefined,
489
- resultMessage: undefined,
490
- agentStateMessage: undefined,
491
- });
492
- }
493
-
494
- return newMessages;
345
+ return this._instance;
495
346
  }
496
347
 
497
- async processRuntimeRequest(request: CopilotRuntimeRequest): Promise<CopilotRuntimeResponse> {
498
- const {
499
- serviceAdapter,
500
- messages: rawMessages,
501
- actions: clientSideActionsInput,
502
- threadId,
503
- runId,
504
- outputMessagesPromise,
505
- graphqlContext,
506
- forwardedParameters,
507
- url,
508
- extensions,
509
- agentSession,
510
- agentStates,
511
- publicApiKey,
512
- context,
513
- } = request;
514
- graphqlContext.request.signal.addEventListener(
515
- "abort",
516
- () =>
517
- this.onStopGeneration?.({
518
- threadId,
519
- runId,
520
- url,
521
- agentName: agentSession?.agentName,
522
- lastMessage: rawMessages[rawMessages.length - 1],
523
- }),
524
- { once: true }, // optional: fire only once
525
- );
526
-
527
- const eventSource = new RuntimeEventSource({
528
- errorHandler: async (error, context) => {
529
- await this.error("error", context, error, publicApiKey);
530
- },
531
- errorContext: {
532
- threadId,
533
- runId,
534
- source: "runtime",
535
- request: {
536
- operation: "processRuntimeRequest",
537
- method: "POST",
538
- url: url,
539
- startTime: Date.now(),
540
- },
541
- agent: agentSession ? { name: agentSession.agentName } : undefined,
542
- technical: {
543
- environment: process.env.NODE_ENV,
544
- },
545
- },
546
- });
547
- // Track request start time for logging
548
- const requestStartTime = Date.now();
549
- // For storing streamed chunks if progressive logging is enabled
550
- const streamedChunks: any[] = [];
551
-
552
- try {
553
- if (
554
- Object.keys(this.agents).length &&
555
- agentSession?.agentName &&
556
- !this.delegateAgentProcessingToServiceAdapter
557
- ) {
558
- this.agents = { [agentSession.agentName]: this.agents[agentSession.agentName] };
559
- }
560
-
561
- if (agentSession && !this.delegateAgentProcessingToServiceAdapter) {
562
- return await this.processAgentRequest(request);
563
- }
564
- if (serviceAdapter instanceof EmptyAdapter) {
565
- throw new CopilotKitMisuseError({
566
- message: `Invalid adapter configuration: EmptyAdapter is only meant to be used with agent lock mode.
567
- For non-agent components like useCopilotChatSuggestions, CopilotTextarea, or CopilotTask,
568
- please use an LLM adapter instead.`,
569
- });
570
- }
571
-
572
- // +++ Get Server Side Actions (including dynamic MCP) EARLY +++
573
- const serverSideActions = await this.getServerSideActions(request);
574
- // --- Get Server Side Actions (including dynamic MCP) EARLY ---
575
-
576
- // Filter raw messages *before* injection
577
- const filteredRawMessages = rawMessages.filter((message) => !message.agentStateMessage);
578
-
579
- // +++ Inject MCP Instructions based on current actions +++
580
- const messagesWithInjectedInstructions = this.injectMCPToolInstructions(
581
- filteredRawMessages,
582
- serverSideActions,
583
- );
584
- const inputMessages = convertGqlInputToMessages(messagesWithInjectedInstructions);
585
- // --- Inject MCP Instructions based on current actions ---
586
-
587
- // Log LLM request if logging is enabled
588
- if (this.observability?.enabled && publicApiKey) {
589
- try {
590
- const requestData: LLMRequestData = {
591
- threadId,
592
- runId,
593
- model: forwardedParameters?.model,
594
- messages: inputMessages,
595
- actions: clientSideActionsInput,
596
- forwardedParameters,
597
- timestamp: requestStartTime,
598
- provider: this.detectProvider(serviceAdapter),
599
- };
600
-
601
- await this.observability.hooks.handleRequest(requestData);
602
- } catch (error) {
603
- console.error("Error logging LLM request:", error);
604
- }
605
- }
606
-
607
- const serverSideActionsInput: ActionInput[] = serverSideActions.map((action) => ({
608
- name: action.name,
609
- description: action.description,
610
- jsonSchema: JSON.stringify(actionParametersToJsonSchema(action.parameters)),
611
- additionalConfig: action.additionalConfig,
612
- }));
613
-
614
- const actionInputs = flattenToolCallsNoDuplicates([
615
- ...serverSideActionsInput,
616
- ...clientSideActionsInput.filter(
617
- // Filter remote actions from CopilotKit core loop
618
- (action) => action.available !== ActionInputAvailability.remote,
619
- ),
620
- ]);
621
-
622
- await this.onBeforeRequest?.({
623
- threadId,
624
- runId,
625
- inputMessages,
626
- properties: graphqlContext.properties,
627
- url,
628
- });
629
-
630
- const result = await serviceAdapter.process({
631
- messages: inputMessages,
632
- actions: actionInputs,
633
- threadId,
634
- runId,
635
- eventSource,
636
- forwardedParameters,
637
- extensions,
638
- agentSession,
639
- agentStates,
640
- });
641
-
642
- // for backwards compatibility, we deal with the case that no threadId is provided
643
- // by the frontend, by using the threadId from the response
644
- const nonEmptyThreadId = threadId ?? result.threadId;
645
-
646
- outputMessagesPromise
647
- .then((outputMessages) => {
648
- this.onAfterRequest?.({
649
- threadId: nonEmptyThreadId,
650
- runId: result.runId,
651
- inputMessages,
652
- outputMessages,
653
- properties: graphqlContext.properties,
654
- url,
348
+ private assignEndpointsToAgents(endpoints: CopilotRuntimeConstructorParams["remoteEndpoints"]) {
349
+ return endpoints.reduce((acc, endpoint) => {
350
+ if (resolveEndpointType(endpoint) == EndpointType.LangGraphPlatform) {
351
+ let lgAgents = {};
352
+ const lgEndpoint = endpoint as LangGraphPlatformEndpoint;
353
+ lgEndpoint.agents.forEach((agent) => {
354
+ const graphId = agent.assistantId ?? agent.name;
355
+ lgAgents[graphId] = new LangGraphAgent({
356
+ deploymentUrl: lgEndpoint.deploymentUrl,
357
+ langsmithApiKey: lgEndpoint.langsmithApiKey,
358
+ graphId,
655
359
  });
656
- })
657
- .catch((_error) => {});
658
-
659
- // After getting the response, log it if logging is enabled
660
- if (this.observability?.enabled && publicApiKey) {
661
- try {
662
- outputMessagesPromise
663
- .then((outputMessages) => {
664
- const responseData: LLMResponseData = {
665
- threadId: result.threadId,
666
- runId: result.runId,
667
- model: forwardedParameters?.model,
668
- // Use collected chunks for progressive mode or outputMessages for regular mode
669
- output: this.observability.progressive ? streamedChunks : outputMessages,
670
- latency: Date.now() - requestStartTime,
671
- timestamp: Date.now(),
672
- provider: this.detectProvider(serviceAdapter),
673
- // Indicate this is the final response
674
- isFinalResponse: true,
675
- };
676
-
677
- try {
678
- this.observability.hooks.handleResponse(responseData);
679
- } catch (logError) {
680
- console.error("Error logging LLM response:", logError);
681
- }
682
- })
683
- .catch((error) => {
684
- console.error("Failed to get output messages for logging:", error);
685
- });
686
- } catch (error) {
687
- console.error("Error setting up logging for LLM response:", error);
688
- }
689
- }
360
+ });
690
361
 
691
- // Add progressive logging if enabled
692
- if (this.observability?.enabled && this.observability.progressive && publicApiKey) {
693
- // Keep reference to original stream function
694
- const originalStream = eventSource.stream.bind(eventSource);
695
-
696
- // Wrap the stream function to intercept events
697
- eventSource.stream = async (callback) => {
698
- await originalStream(async (eventStream$) => {
699
- // Create subscription to capture streaming events
700
- eventStream$.subscribe({
701
- next: (event) => {
702
- // Only log content chunks
703
- if (event.type === RuntimeEventTypes.TextMessageContent) {
704
- // Store the chunk
705
- streamedChunks.push(event.content);
706
-
707
- // Log each chunk separately for progressive mode
708
- try {
709
- const progressiveData: LLMResponseData = {
710
- threadId: threadId || "",
711
- runId,
712
- model: forwardedParameters?.model,
713
- output: event.content,
714
- latency: Date.now() - requestStartTime,
715
- timestamp: Date.now(),
716
- provider: this.detectProvider(serviceAdapter),
717
- isProgressiveChunk: true,
718
- };
719
-
720
- // Use Promise to handle async logger without awaiting
721
- Promise.resolve()
722
- .then(() => {
723
- this.observability.hooks.handleResponse(progressiveData);
724
- })
725
- .catch((error) => {
726
- console.error("Error in progressive logging:", error);
727
- });
728
- } catch (error) {
729
- console.error("Error preparing progressive log data:", error);
730
- }
731
- }
732
- },
733
- });
734
-
735
- // Call the original callback with the event stream
736
- await callback(eventStream$);
737
- });
362
+ return {
363
+ ...acc,
364
+ ...lgAgents,
738
365
  };
739
366
  }
740
367
 
741
- return {
742
- threadId: nonEmptyThreadId,
743
- runId: result.runId,
744
- eventSource,
745
- serverSideActions,
746
- actionInputsWithoutAgents: actionInputs.filter(
747
- (action) =>
748
- // TODO-AGENTS: do not exclude ALL server side actions
749
- !serverSideActions.find((serverSideAction) => serverSideAction.name == action.name),
750
- // !isRemoteAgentAction(
751
- // serverSideActions.find((serverSideAction) => serverSideAction.name == action.name),
752
- // ),
753
- ),
754
- extensions: result.extensions,
755
- };
756
- } catch (error) {
757
- // Log error if logging is enabled
758
- if (this.observability?.enabled && publicApiKey) {
759
- try {
760
- const errorData: LLMErrorData = {
761
- threadId,
762
- runId,
763
- model: forwardedParameters?.model,
764
- error: error instanceof Error ? error : String(error),
765
- timestamp: Date.now(),
766
- latency: Date.now() - requestStartTime,
767
- provider: this.detectProvider(serviceAdapter),
768
- };
769
-
770
- await this.observability.hooks.handleError(errorData);
771
- } catch (logError) {
772
- console.error("Error logging LLM error:", logError);
773
- }
774
- }
775
-
776
- let structuredError: CopilotKitError;
777
-
778
- if (error instanceof CopilotKitError) {
779
- structuredError = error;
780
- } else {
781
- // Convert non-CopilotKitErrors to structured errors, but preserve already structured ones
782
- structuredError = ensureStructuredError(error, (err) =>
783
- this.convertStreamingErrorToStructured(err),
784
- );
785
- }
786
-
787
- // Track the error
788
- await this.error(
789
- "error",
790
- {
791
- threadId,
792
- runId,
793
- source: "runtime",
794
- request: {
795
- operation: "processRuntimeRequest",
796
- method: "POST",
797
- url: url,
798
- startTime: requestStartTime,
799
- },
800
- response: {
801
- endTime: Date.now(),
802
- latency: Date.now() - requestStartTime,
803
- },
804
- agent: agentSession ? { name: agentSession.agentName } : undefined,
805
- technical: {
806
- environment: process.env.NODE_ENV,
807
- stackTrace: error instanceof Error ? error.stack : undefined,
808
- },
809
- },
810
- structuredError,
811
- publicApiKey,
812
- );
813
-
814
- throw structuredError;
815
- }
368
+ return acc;
369
+ }, {});
816
370
  }
817
371
 
818
- async getAllAgents(graphqlContext: GraphQLContext): Promise<(AgentWithEndpoint | Agent)[]> {
819
- const agentsWithEndpoints = await this.discoverAgentsFromEndpoints(graphqlContext);
820
- const aguiAgents = this.discoverAgentsFromAgui();
821
-
822
- this.availableAgents = [...agentsWithEndpoints, ...aguiAgents].map((a) => ({
823
- name: a.name,
824
- id: a.id,
825
- }));
826
-
827
- return [...agentsWithEndpoints, ...aguiAgents];
828
- }
372
+ handleServiceAdapter(serviceAdapter: CopilotServiceAdapter) {
373
+ this.runtimeArgs.agents = Promise.resolve(this.runtimeArgs.agents ?? {}).then(
374
+ async (agents) => {
375
+ let agentsList = agents;
376
+ const isAgentsListEmpty = !Object.keys(agents).length;
377
+ const hasServiceAdapter = Boolean(serviceAdapter);
378
+ const illegalServiceAdapterNames = ["EmptyAdapter"];
379
+ const serviceAdapterCanBeUsedForAgent = !illegalServiceAdapterNames.includes(
380
+ serviceAdapter.name,
381
+ );
829
382
 
830
- async discoverAgentsFromEndpoints(graphqlContext: GraphQLContext): Promise<AgentWithEndpoint[]> {
831
- const agents: Promise<AgentWithEndpoint[]> = this.remoteEndpointDefinitions.reduce(
832
- async (acc: Promise<Agent[]>, endpoint) => {
833
- const agents = await acc;
834
- if (endpoint.type === EndpointType.LangGraphPlatform) {
835
- const propertyHeaders = graphqlContext.properties.authorization
836
- ? { authorization: `Bearer ${graphqlContext.properties.authorization}` }
837
- : null;
838
-
839
- const client = new LangGraphClient({
840
- apiUrl: endpoint.deploymentUrl,
841
- apiKey: endpoint.langsmithApiKey,
842
- defaultHeaders: { ...propertyHeaders },
383
+ if (isAgentsListEmpty && (!hasServiceAdapter || !serviceAdapterCanBeUsedForAgent)) {
384
+ throw new CopilotKitMisuseError({
385
+ message:
386
+ "No default agent provided. Please provide a default agent in the runtime config.",
843
387
  });
844
- let data: Array<{ assistant_id: string; graph_id: string }> | { detail: string } = [];
845
- try {
846
- data = await client.assistants.search();
847
-
848
- if (data && "detail" in data && (data.detail as string).toLowerCase() === "not found") {
849
- throw new CopilotKitAgentDiscoveryError({ availableAgents: this.availableAgents });
850
- }
851
- } catch (e) {
852
- throw new CopilotKitMisuseError({
853
- message: `
854
- Failed to find or contact remote endpoint at url ${endpoint.deploymentUrl}.
855
- Make sure the API is running and that it's indeed a LangGraph platform url.
856
-
857
- See more: https://docs.copilotkit.ai/troubleshooting/common-issues`,
858
- });
859
- }
860
- const endpointAgents = data.map((entry) => ({
861
- name: entry.graph_id,
862
- id: entry.assistant_id,
863
- description: "",
864
- endpoint,
865
- }));
866
- return [...agents, ...endpointAgents];
867
388
  }
868
389
 
869
- interface InfoResponse {
870
- agents?: Array<{
871
- name: string;
872
- description: string;
873
- }>;
874
- }
875
- const cpkEndpoint = endpoint as CopilotKitEndpoint;
876
- const fetchUrl = `${endpoint.url}/info`;
877
- try {
878
- const response = await fetchWithRetry(fetchUrl, {
879
- method: "POST",
880
- headers: createHeaders(cpkEndpoint.onBeforeRequest, graphqlContext),
881
- body: JSON.stringify({ properties: graphqlContext.properties }),
390
+ if (isAgentsListEmpty) {
391
+ agentsList.default = new BasicAgent({
392
+ model: `${serviceAdapter.provider}/${serviceAdapter.model}`,
882
393
  });
883
- if (!response.ok) {
884
- if (response.status === 404) {
885
- throw new CopilotKitApiDiscoveryError({ url: fetchUrl });
886
- }
887
- throw new ResolvedCopilotKitError({
888
- status: response.status,
889
- url: fetchUrl,
890
- isRemoteEndpoint: true,
891
- });
892
- }
394
+ }
893
395
 
894
- const data: InfoResponse = await response.json();
895
- const endpointAgents = (data?.agents ?? []).map((agent) => ({
896
- name: agent.name,
897
- description: agent.description ?? "" ?? "",
898
- id: randomId(), // Required by Agent type
899
- endpoint,
900
- }));
901
- return [...agents, ...endpointAgents];
902
- } catch (error) {
903
- if (error instanceof CopilotKitError) {
904
- throw error;
905
- }
906
- throw new CopilotKitLowLevelError({ error: error as Error, url: fetchUrl });
396
+ if (this.params.actions?.length) {
397
+ const mcpTools = await this.getToolsFromMCP();
398
+ agentsList = this.assignToolsToAgents(agents, [
399
+ ...this.getToolsFromActions(this.params.actions),
400
+ ...mcpTools,
401
+ ]);
907
402
  }
403
+
404
+ return agentsList;
908
405
  },
909
- Promise.resolve([]),
910
406
  );
911
-
912
- return agents;
913
407
  }
914
408
 
915
- discoverAgentsFromAgui(): Agent[] {
916
- return Object.entries(this.agents ?? []).map(([key, agent]: [string, AbstractAgent]) => ({
917
- name: (agent as any).agentName ?? key,
918
- id: agent.agentId ?? key,
919
- description: agent.description ?? "",
920
- }));
921
- }
922
-
923
- async loadAgentState(
924
- graphqlContext: GraphQLContext,
925
- threadId: string,
926
- agentName: string,
927
- ): Promise<LoadAgentStateResponse> {
928
- const agents = await this.getAllAgents(graphqlContext);
929
-
930
- const agent = agents.find((agent) => agent.name === agentName);
931
- if (!agent) {
932
- throw new Error("Agent not found");
933
- }
934
-
935
- if (
936
- "endpoint" in agent &&
937
- (agent.endpoint.type === EndpointType.CopilotKit || !("type" in agent.endpoint))
938
- ) {
939
- const cpkEndpoint = agent.endpoint as CopilotKitEndpoint;
940
- const fetchUrl = `${cpkEndpoint.url}/agents/state`;
941
- try {
942
- const response = await fetchWithRetry(fetchUrl, {
943
- method: "POST",
944
- headers: createHeaders(cpkEndpoint.onBeforeRequest, graphqlContext),
945
- body: JSON.stringify({
946
- properties: graphqlContext.properties,
947
- threadId,
948
- name: agentName,
949
- }),
950
- });
951
- if (!response.ok) {
952
- if (response.status === 404) {
953
- throw new CopilotKitApiDiscoveryError({ url: fetchUrl });
954
- }
955
-
956
- // Extract semantic error information from response body
957
- let errorMessage = `HTTP ${response.status} error`;
958
- try {
959
- const errorBody = await response.text();
960
- const parsedError = JSON.parse(errorBody);
961
- if (parsedError.error && typeof parsedError.error === "string") {
962
- errorMessage = parsedError.error;
963
- }
964
- } catch {
965
- // If parsing fails, fall back to generic message
966
- }
409
+ // Receive this.params.action and turn it into the AbstractAgent tools
410
+ private getToolsFromActions(
411
+ actions: ActionsConfiguration<any>,
412
+ ): BasicAgentConfiguration["tools"] {
413
+ // Resolve actions to an array (handle function case)
414
+ const actionsArray =
415
+ typeof actions === "function" ? actions({ properties: {}, url: undefined }) : actions;
967
416
 
968
- throw new ResolvedCopilotKitError({
969
- status: response.status,
970
- url: fetchUrl,
971
- isRemoteEndpoint: true,
972
- message: errorMessage,
973
- });
974
- }
417
+ // Convert each Action to a ToolDefinition
418
+ return actionsArray.map((action) => {
419
+ // Convert JSON schema to Zod schema
420
+ const zodSchema = getZodParameters(action.parameters || []);
975
421
 
976
- const data: LoadAgentStateResponse = await response.json();
977
-
978
- return {
979
- ...data,
980
- state: JSON.stringify(data.state),
981
- messages: JSON.stringify(data.messages),
982
- };
983
- } catch (error) {
984
- if (error instanceof CopilotKitError) {
985
- throw error;
986
- }
987
- throw new CopilotKitLowLevelError({ error, url: fetchUrl });
988
- }
989
- }
990
-
991
- const propertyHeaders = graphqlContext.properties.authorization
992
- ? { authorization: `Bearer ${graphqlContext.properties.authorization}` }
993
- : null;
994
-
995
- let state: any = {};
996
- try {
997
- let client: LangGraphClient | null;
998
- if ("endpoint" in agent && agent.endpoint.type === EndpointType.LangGraphPlatform) {
999
- client = new LangGraphClient({
1000
- apiUrl: agent.endpoint.deploymentUrl,
1001
- apiKey: agent.endpoint.langsmithApiKey,
1002
- defaultHeaders: { ...propertyHeaders },
1003
- });
1004
- } else {
1005
- const aguiAgent = graphqlContext._copilotkit.runtime.agents[agent.name] as LangGraphAgent;
1006
- if (!aguiAgent) {
1007
- throw new Error(`Agent: ${agent.name} could not be resolved`);
1008
- }
1009
- // @ts-expect-error -- both clients are the same
1010
- client = aguiAgent.client ?? null;
1011
- }
1012
-
1013
- state = client ? ((await client.threads.getState(threadId)).values as any) : {};
1014
- } catch (error) {
1015
- // All errors from agent state loading are user configuration issues
1016
- const errorMessage = error instanceof Error ? error.message : String(error);
1017
- const errorStatus = error?.response?.status || error?.status;
1018
-
1019
- if (errorStatus === 404) {
1020
- state = {};
1021
- } else {
1022
- // Log user configuration errors at debug level to reduce noise
1023
- console.debug(`Agent '${agentName}' configuration issue: ${errorMessage}`);
1024
-
1025
- // Throw a configuration error - all agent state loading failures are user setup issues
1026
- throw new ResolvedCopilotKitError({
1027
- status: 400,
1028
- message: `Agent '${agentName}' failed to execute: ${errorMessage}`,
1029
- code: CopilotKitErrorCode.CONFIGURATION_ERROR,
1030
- });
1031
- }
1032
- }
1033
-
1034
- if (Object.keys(state).length === 0) {
1035
422
  return {
1036
- threadId: threadId || "",
1037
- threadExists: false,
1038
- state: JSON.stringify({}),
1039
- messages: JSON.stringify([]),
1040
- };
1041
- } else {
1042
- const { messages, ...stateWithoutMessages } = state;
1043
- const copilotkitMessages = langchainMessagesToCopilotKit(messages);
1044
- return {
1045
- threadId: threadId || "",
1046
- threadExists: true,
1047
- state: JSON.stringify(stateWithoutMessages),
1048
- messages: JSON.stringify(copilotkitMessages),
423
+ name: action.name,
424
+ description: action.description || "",
425
+ parameters: zodSchema,
1049
426
  };
1050
- }
1051
-
1052
- throw new Error(`Agent: ${agent.name} could not be resolved`);
427
+ });
1053
428
  }
1054
429
 
1055
- private async processAgentRequest(
1056
- request: CopilotRuntimeRequest,
1057
- ): Promise<CopilotRuntimeResponse> {
1058
- const {
1059
- messages: rawMessages,
1060
- outputMessagesPromise,
1061
- graphqlContext,
1062
- agentSession,
1063
- threadId: threadIdFromRequest,
1064
- metaEvents,
1065
- publicApiKey,
1066
- forwardedParameters,
1067
- context,
1068
- } = request;
1069
- const { agentName, nodeName } = agentSession;
1070
-
1071
- // Track request start time for observability
1072
- const requestStartTime = Date.now();
1073
- // For storing streamed chunks if progressive logging is enabled
1074
- const streamedChunks: any[] = [];
1075
-
1076
- // for backwards compatibility, deal with the case when no threadId is provided
1077
- const threadId = threadIdFromRequest ?? agentSession.threadId;
1078
-
1079
- // Track agent request start
1080
- await this.error(
1081
- "agent_state",
1082
- {
1083
- threadId,
1084
- source: "agent",
1085
- request: {
1086
- operation: "processAgentRequest",
1087
- method: "POST",
1088
- startTime: requestStartTime,
1089
- },
1090
- agent: {
1091
- name: agentName,
1092
- nodeName: nodeName,
1093
- },
1094
- messages: {
1095
- input: rawMessages,
1096
- messageCount: rawMessages.length,
1097
- },
1098
- technical: {
1099
- environment: process.env.NODE_ENV,
1100
- },
1101
- },
1102
- undefined,
1103
- publicApiKey,
1104
- );
1105
-
1106
- const serverSideActions = await this.getServerSideActions(request);
1107
-
1108
- const messages = convertGqlInputToMessages(rawMessages);
1109
-
1110
- const currentAgent = serverSideActions.find(
1111
- (action) => action.name === agentName && isRemoteAgentAction(action),
1112
- ) as RemoteAgentAction;
1113
-
1114
- if (!currentAgent) {
1115
- throw new CopilotKitAgentDiscoveryError({ agentName, availableAgents: this.availableAgents });
430
+ private assignToolsToAgents(
431
+ agents: Record<string, AbstractAgent>,
432
+ tools: BasicAgentConfiguration["tools"],
433
+ ): Record<string, AbstractAgent> {
434
+ if (!tools?.length) {
435
+ return agents;
1116
436
  }
1117
437
 
1118
- // Filter actions to include:
1119
- // 1. Regular (non-agent) actions
1120
- // 2. Other agents' actions (but prevent self-calls to avoid infinite loops)
1121
- const availableActionsForCurrentAgent: ActionInput[] = serverSideActions
1122
- .filter(
1123
- (action) =>
1124
- // Case 1: Keep all regular (non-agent) actions
1125
- !isRemoteAgentAction(action) ||
1126
- // Case 2: For agent actions, keep all except self (prevent infinite loops)
1127
- (isRemoteAgentAction(action) && action.name !== agentName) /* prevent self-calls */,
1128
- )
1129
- .map((action) => ({
1130
- name: action.name,
1131
- description: action.description,
1132
- jsonSchema: JSON.stringify(actionParametersToJsonSchema(action.parameters)),
1133
- }));
438
+ const enrichedAgents: Record<string, AbstractAgent> = { ...agents };
1134
439
 
1135
- const allAvailableActions = flattenToolCallsNoDuplicates([
1136
- ...availableActionsForCurrentAgent,
1137
- ...request.actions,
1138
- ]);
440
+ for (const [agentId, agent] of Object.entries(enrichedAgents)) {
441
+ const existingConfig = (Reflect.get(agent, "config") ?? {}) as BasicAgentConfiguration;
442
+ const existingTools = existingConfig.tools ?? [];
1139
443
 
1140
- // Log agent request if observability is enabled
1141
- if (this.observability?.enabled && publicApiKey) {
1142
- try {
1143
- const requestData: LLMRequestData = {
1144
- threadId,
1145
- runId: undefined,
1146
- model: forwardedParameters?.model,
1147
- messages,
1148
- actions: allAvailableActions,
1149
- forwardedParameters,
1150
- timestamp: requestStartTime,
1151
- provider: "agent",
1152
- agentName, // Add agent-specific context
1153
- nodeName,
1154
- };
444
+ const updatedConfig: BasicAgentConfiguration = {
445
+ ...existingConfig,
446
+ tools: [...existingTools, ...tools],
447
+ };
1155
448
 
1156
- await this.observability.hooks.handleRequest(requestData);
1157
- } catch (error) {
1158
- console.error("Error logging agent request:", error);
1159
- }
449
+ Reflect.set(agent, "config", updatedConfig);
450
+ enrichedAgents[agentId] = agent;
1160
451
  }
1161
452
 
1162
- await this.onBeforeRequest?.({
1163
- threadId,
1164
- runId: undefined,
1165
- inputMessages: messages,
1166
- properties: graphqlContext.properties,
1167
- });
1168
-
1169
- try {
1170
- const eventSource = new RuntimeEventSource({
1171
- errorHandler: async (error, context) => {
1172
- await this.error("error", context, error, publicApiKey);
1173
- },
1174
- errorContext: {
1175
- threadId,
1176
- source: "agent",
1177
- request: {
1178
- operation: "processAgentRequest",
1179
- method: "POST",
1180
- startTime: requestStartTime,
1181
- },
1182
- agent: {
1183
- name: agentName,
1184
- nodeName: nodeName,
1185
- },
1186
- technical: {
1187
- environment: process.env.NODE_ENV,
1188
- },
1189
- },
1190
- });
1191
- const stream = await currentAgent.remoteAgentHandler({
1192
- name: agentName,
1193
- threadId,
1194
- nodeName,
1195
- metaEvents,
1196
- actionInputsWithoutAgents: allAvailableActions,
1197
- });
1198
-
1199
- // Add progressive observability if enabled
1200
- if (this.observability?.enabled && this.observability.progressive && publicApiKey) {
1201
- // Wrap the stream function to intercept events for observability without changing core logic
1202
- const originalStream = eventSource.stream.bind(eventSource);
1203
-
1204
- eventSource.stream = async (callback) => {
1205
- await originalStream(async (eventStream$) => {
1206
- // Create subscription to capture streaming events
1207
- eventStream$.subscribe({
1208
- next: (event) => {
1209
- // Only log content chunks
1210
- if (event.type === RuntimeEventTypes.TextMessageContent) {
1211
- // Store the chunk
1212
- streamedChunks.push(event.content);
1213
-
1214
- // Log each chunk separately for progressive mode
1215
- try {
1216
- const progressiveData: LLMResponseData = {
1217
- threadId: threadId || "",
1218
- runId: undefined,
1219
- model: forwardedParameters?.model,
1220
- output: event.content,
1221
- latency: Date.now() - requestStartTime,
1222
- timestamp: Date.now(),
1223
- provider: "agent",
1224
- isProgressiveChunk: true,
1225
- agentName,
1226
- nodeName,
1227
- };
1228
-
1229
- // Use Promise to handle async logger without awaiting
1230
- Promise.resolve()
1231
- .then(() => {
1232
- this.observability.hooks.handleResponse(progressiveData);
1233
- })
1234
- .catch((error) => {
1235
- console.error("Error in progressive agent logging:", error);
1236
- });
1237
- } catch (error) {
1238
- console.error("Error preparing progressive agent log data:", error);
1239
- }
1240
- }
1241
- },
1242
- });
1243
-
1244
- // Call the original callback with the event stream
1245
- await callback(eventStream$);
1246
- });
1247
- };
1248
- }
453
+ return enrichedAgents;
454
+ }
1249
455
 
1250
- eventSource.stream(async (eventStream$) => {
1251
- from(stream).subscribe({
1252
- next: (event) => eventStream$.next(event),
1253
- error: async (err) => {
1254
- // Log error with observability if enabled
1255
- if (this.observability?.enabled && publicApiKey) {
1256
- try {
1257
- const errorData: LLMErrorData = {
1258
- threadId,
1259
- runId: undefined,
1260
- model: forwardedParameters?.model,
1261
- error: err instanceof Error ? err : String(err),
1262
- timestamp: Date.now(),
1263
- latency: Date.now() - requestStartTime,
1264
- provider: "agent",
1265
- agentName,
1266
- nodeName,
1267
- };
1268
-
1269
- this.observability.hooks.handleError(errorData);
1270
- } catch (logError) {
1271
- console.error("Error logging agent error:", logError);
1272
- }
456
+ private createOnBeforeRequestHandler(
457
+ params?: CopilotRuntimeConstructorParams & PartialBy<CopilotRuntimeOptions, "agents">,
458
+ ) {
459
+ return async (hookParams: BeforeRequestMiddlewareFnParameters[0]) => {
460
+ // TODO: get public api key and run with expected data
461
+ // if (this.observability?.enabled && this.params.publicApiKey) {
462
+ // this.logObservabilityBeforeRequest()
463
+ // }
464
+
465
+ // TODO: replace hooksParams top argument type with BeforeRequestMiddlewareParameters when exported
466
+ params?.beforeRequestMiddleware?.(hookParams);
467
+
468
+ if (params?.middleware?.onBeforeRequest) {
469
+ const { request, runtime, path } = hookParams;
470
+ const body = (await readBody(request)) as RunAgentInput;
471
+ const gqlMessages = (aguiToGQL(body.messages) as Message[]).reduce(
472
+ (acc, msg) => {
473
+ if ("role" in msg && msg.role === "user") {
474
+ acc.inputMessages.push(msg);
475
+ } else {
476
+ acc.outputMessages.push(msg);
1273
477
  }
1274
-
1275
- // Preserve structured CopilotKit errors, only convert unstructured errors
1276
- const structuredError = ensureStructuredError(err, (error) =>
1277
- this.convertStreamingErrorToStructured(error),
1278
- );
1279
-
1280
- // Track streaming errors
1281
- await this.error(
1282
- "error",
1283
- {
1284
- threadId,
1285
- source: "agent",
1286
- request: {
1287
- operation: "processAgentRequest",
1288
- method: "POST",
1289
- startTime: requestStartTime,
1290
- },
1291
- response: {
1292
- endTime: Date.now(),
1293
- latency: Date.now() - requestStartTime,
1294
- },
1295
- agent: {
1296
- name: agentName,
1297
- nodeName: nodeName,
1298
- },
1299
- technical: {
1300
- environment: process.env.NODE_ENV,
1301
- stackTrace: err instanceof Error ? err.stack : undefined,
1302
- },
1303
- },
1304
- structuredError,
1305
- publicApiKey,
1306
- );
1307
-
1308
- eventStream$.error(structuredError);
1309
- eventStream$.complete();
478
+ return acc;
1310
479
  },
1311
- complete: () => eventStream$.complete(),
1312
- });
1313
- });
1314
-
1315
- // Log final agent response when outputs are available
1316
- if (this.observability?.enabled && publicApiKey) {
1317
- outputMessagesPromise
1318
- .then((outputMessages) => {
1319
- const responseData: LLMResponseData = {
1320
- threadId,
1321
- runId: undefined,
1322
- model: forwardedParameters?.model,
1323
- // Use collected chunks for progressive mode or outputMessages for regular mode
1324
- output: this.observability.progressive ? streamedChunks : outputMessages,
1325
- latency: Date.now() - requestStartTime,
1326
- timestamp: Date.now(),
1327
- provider: "agent",
1328
- isFinalResponse: true,
1329
- agentName,
1330
- nodeName,
1331
- };
1332
-
1333
- try {
1334
- this.observability.hooks.handleResponse(responseData);
1335
- } catch (logError) {
1336
- console.error("Error logging agent response:", logError);
1337
- }
1338
- })
1339
- .catch((error) => {
1340
- console.error("Failed to get output messages for agent logging:", error);
1341
- });
480
+ { inputMessages: [] as Message[], outputMessages: [] as Message[] },
481
+ );
482
+ const { inputMessages, outputMessages } = gqlMessages;
483
+ params.middleware.onBeforeRequest({
484
+ threadId: body.threadId,
485
+ runId: body.runId,
486
+ inputMessages,
487
+ properties: body.forwardedProps,
488
+ url: request.url,
489
+ } satisfies OnBeforeRequestOptions);
1342
490
  }
491
+ };
492
+ }
1343
493
 
1344
- outputMessagesPromise
1345
- .then((outputMessages) => {
1346
- this.onAfterRequest?.({
1347
- threadId,
1348
- runId: undefined,
1349
- inputMessages: messages,
1350
- outputMessages,
1351
- properties: graphqlContext.properties,
1352
- });
1353
- })
1354
- .catch((_error) => {});
1355
-
1356
- return {
1357
- threadId,
1358
- runId: undefined,
1359
- eventSource,
1360
- serverSideActions,
1361
- actionInputsWithoutAgents: allAvailableActions,
1362
- };
1363
- } catch (error) {
1364
- // Log error with observability if enabled
1365
- if (this.observability?.enabled && publicApiKey) {
1366
- try {
1367
- const errorData: LLMErrorData = {
1368
- threadId,
1369
- runId: undefined,
1370
- model: forwardedParameters?.model,
1371
- error: error instanceof Error ? error : String(error),
1372
- timestamp: Date.now(),
1373
- latency: Date.now() - requestStartTime,
1374
- provider: "agent",
1375
- agentName,
1376
- nodeName,
1377
- };
1378
-
1379
- await this.observability.hooks.handleError(errorData);
1380
- } catch (logError) {
1381
- console.error("Error logging agent error:", logError);
1382
- }
494
+ private createOnAfterRequestHandler(
495
+ params?: CopilotRuntimeConstructorParams & PartialBy<CopilotRuntimeOptions, "agents">,
496
+ ) {
497
+ return async (hookParams: AfterRequestMiddlewareFnParameters[0]) => {
498
+ // TODO: get public api key and run with expected data
499
+ // if (this.observability?.enabled && publicApiKey) {
500
+ // this.logObservabilityAfterRequest()
501
+ // }
502
+
503
+ // TODO: replace hooksParams top argument type with AfterRequestMiddlewareParameters when exported
504
+ params?.afterRequestMiddleware?.(hookParams);
505
+
506
+ if (params?.middleware?.onAfterRequest) {
507
+ // TODO: provide old expected params here when available
508
+ // @ts-expect-error -- missing arguments.
509
+ params.middleware.onAfterRequest({});
1383
510
  }
1384
-
1385
- // Ensure error is structured
1386
- const structuredError = ensureStructuredError(error, (err) =>
1387
- this.convertStreamingErrorToStructured(err),
1388
- );
1389
-
1390
- // Track the agent error
1391
- await this.error(
1392
- "error",
1393
- {
1394
- threadId,
1395
- source: "agent",
1396
- request: {
1397
- operation: "processAgentRequest",
1398
- method: "POST",
1399
- startTime: requestStartTime,
1400
- },
1401
- response: {
1402
- endTime: Date.now(),
1403
- latency: Date.now() - requestStartTime,
1404
- },
1405
- agent: {
1406
- name: agentName,
1407
- nodeName: nodeName,
1408
- },
1409
- technical: {
1410
- environment: process.env.NODE_ENV,
1411
- stackTrace: error instanceof Error ? error.stack : undefined,
1412
- },
1413
- },
1414
- structuredError,
1415
- publicApiKey,
1416
- );
1417
-
1418
- console.error("Error getting response:", error);
1419
- throw structuredError;
1420
- }
511
+ };
1421
512
  }
1422
513
 
1423
- private async getServerSideActions(request: CopilotRuntimeRequest): Promise<Action<any>[]> {
1424
- const { graphqlContext, messages: rawMessages, agentStates, url } = request;
514
+ // Observability Methods
1425
515
 
1426
- // --- Standard Action Fetching (unchanged) ---
1427
- const inputMessages = convertGqlInputToMessages(rawMessages);
1428
- const langserveFunctions: Action<any>[] = [];
1429
- for (const chainPromise of this.langserve) {
1430
- try {
1431
- const chain = await chainPromise;
1432
- langserveFunctions.push(chain);
1433
- } catch (error) {
1434
- console.error("Error loading langserve chain:", error);
1435
- }
516
+ /**
517
+ * Log LLM request if observability is enabled
518
+ */
519
+ private async logObservabilityBeforeRequest(requestData: LLMRequestData): Promise<void> {
520
+ try {
521
+ await this.observability.hooks.handleRequest(requestData);
522
+ } catch (error) {
523
+ console.error("Error logging LLM request:", error);
1436
524
  }
525
+ }
1437
526
 
1438
- const remoteEndpointDefinitions = this.remoteEndpointDefinitions.map(
1439
- (endpoint) => ({ ...endpoint, type: resolveEndpointType(endpoint) }) as EndpointDefinition,
1440
- );
1441
-
1442
- const remoteActions = await setupRemoteActions({
1443
- remoteEndpointDefinitions,
1444
- graphqlContext,
1445
- messages: inputMessages,
1446
- agentStates,
1447
- frontendUrl: url,
1448
- agents: this.agents,
1449
- metaEvents: request.metaEvents,
1450
- nodeName: request.agentSession?.nodeName,
1451
- context: request.context,
1452
- });
1453
-
1454
- const configuredActions =
1455
- typeof this.actions === "function"
1456
- ? this.actions({ properties: graphqlContext.properties, url })
1457
- : this.actions;
1458
- // --- Standard Action Fetching (unchanged) ---
1459
-
1460
- // +++ Dynamic MCP Action Fetching +++
1461
- const requestSpecificMCPActions: Action<any>[] = [];
1462
- if (this.createMCPClientImpl) {
1463
- // 1. Determine effective MCP endpoints for this request
1464
- const baseEndpoints = this.mcpServersConfig || [];
1465
- // Assuming frontend passes config via properties.mcpServers
1466
- const requestEndpoints = (graphqlContext.properties?.mcpServers ||
1467
- graphqlContext.properties?.mcpEndpoints ||
1468
- []) as MCPEndpointConfig[];
1469
-
1470
- // Merge and deduplicate endpoints based on URL
1471
- const effectiveEndpointsMap = new Map<string, MCPEndpointConfig>();
1472
-
1473
- // First add base endpoints (from runtime configuration)
1474
- [...baseEndpoints].forEach((ep) => {
1475
- if (ep && ep.endpoint) {
1476
- effectiveEndpointsMap.set(ep.endpoint, ep);
1477
- }
1478
- });
1479
-
1480
- // Then add request endpoints (from frontend), which will override duplicates
1481
- [...requestEndpoints].forEach((ep) => {
1482
- if (ep && ep.endpoint) {
1483
- effectiveEndpointsMap.set(ep.endpoint, ep);
1484
- }
1485
- });
1486
-
1487
- const effectiveEndpoints = Array.from(effectiveEndpointsMap.values());
1488
-
1489
- // 2. Fetch/Cache actions for effective endpoints
1490
- for (const config of effectiveEndpoints) {
1491
- const endpointUrl = config.endpoint;
1492
- let actionsForEndpoint: Action<any>[] | undefined = this.mcpActionCache.get(endpointUrl);
527
+ /**
528
+ * Log final LLM response after request completes
529
+ */
530
+ private logObservabilityAfterRequest(
531
+ outputMessagesPromise: Promise<Message[]>,
532
+ baseData: {
533
+ threadId: string;
534
+ runId?: string;
535
+ model?: string;
536
+ provider?: string;
537
+ agentName?: string;
538
+ nodeName?: string;
539
+ },
540
+ streamedChunks: any[],
541
+ requestStartTime: number,
542
+ publicApiKey?: string,
543
+ ): void {
544
+ try {
545
+ outputMessagesPromise
546
+ .then((outputMessages) => {
547
+ const responseData: LLMResponseData = {
548
+ threadId: baseData.threadId,
549
+ runId: baseData.runId,
550
+ model: baseData.model,
551
+ // Use collected chunks for progressive mode or outputMessages for regular mode
552
+ output: this.observability.progressive ? streamedChunks : outputMessages,
553
+ latency: Date.now() - requestStartTime,
554
+ timestamp: Date.now(),
555
+ provider: baseData.provider,
556
+ isFinalResponse: true,
557
+ agentName: baseData.agentName,
558
+ nodeName: baseData.nodeName,
559
+ };
1493
560
 
1494
- if (!actionsForEndpoint) {
1495
- // Not cached, fetch now
1496
- let client: MCPClient | null = null;
1497
561
  try {
1498
- client = await this.createMCPClientImpl(config);
1499
- const tools = await client.tools();
1500
- actionsForEndpoint = convertMCPToolsToActions(tools, endpointUrl);
1501
- this.mcpActionCache.set(endpointUrl, actionsForEndpoint); // Store in cache
1502
- } catch (error) {
1503
- console.error(
1504
- `MCP: Failed to fetch tools from endpoint ${endpointUrl}. Skipping. Error:`,
1505
- error,
1506
- );
1507
- actionsForEndpoint = []; // Assign empty array on error to prevent re-fetching constantly
1508
- this.mcpActionCache.set(endpointUrl, actionsForEndpoint); // Cache the failure (empty array)
562
+ this.observability.hooks.handleResponse(responseData);
563
+ } catch (logError) {
564
+ console.error("Error logging LLM response:", logError);
1509
565
  }
1510
- }
1511
- requestSpecificMCPActions.push(...(actionsForEndpoint || []));
1512
- }
566
+ })
567
+ .catch((error) => {
568
+ console.error("Failed to get output messages for logging:", error);
569
+ });
570
+ } catch (error) {
571
+ console.error("Error setting up logging for LLM response:", error);
1513
572
  }
1514
- // --- Dynamic MCP Action Fetching ---
1515
-
1516
- // Combine all action sources, including the dynamically fetched MCP actions
1517
- return [
1518
- ...configuredActions,
1519
- ...langserveFunctions,
1520
- ...remoteActions,
1521
- ...requestSpecificMCPActions,
1522
- ];
1523
573
  }
1524
574
 
1525
- // Add helper method to detect provider
1526
- private detectProvider(serviceAdapter: CopilotServiceAdapter): string | undefined {
1527
- const adapterName = serviceAdapter.constructor.name;
1528
- if (adapterName.includes("OpenAI")) return "openai";
1529
- if (adapterName.includes("Anthropic")) return "anthropic";
1530
- if (adapterName.includes("Google")) return "google";
1531
- if (adapterName.includes("Groq")) return "groq";
1532
- if (adapterName.includes("LangChain")) return "langchain";
1533
- return undefined;
1534
- }
575
+ // Resolve MCP tools to BasicAgent tool definitions
576
+ // Optionally accepts request-scoped properties to merge request-provided mcpServers
577
+ private async getToolsFromMCP(options?: {
578
+ properties?: Record<string, unknown>;
579
+ }): Promise<BasicAgentConfiguration["tools"]> {
580
+ const runtimeMcpServers = (this.params?.mcpServers ?? []) as MCPEndpointConfig[];
581
+ const createMCPClient = this.params?.createMCPClient as CreateMCPClientFunction | undefined;
582
+
583
+ // If no runtime config and no request overrides, nothing to do
584
+ const requestMcpServers = ((
585
+ options?.properties as { mcpServers?: MCPEndpointConfig[] } | undefined
586
+ )?.mcpServers ??
587
+ (options?.properties as { mcpEndpoints?: MCPEndpointConfig[] } | undefined)?.mcpEndpoints ??
588
+ []) as MCPEndpointConfig[];
589
+
590
+ const hasAnyServers =
591
+ (runtimeMcpServers?.length ?? 0) > 0 || (requestMcpServers?.length ?? 0) > 0;
592
+ if (!hasAnyServers) {
593
+ return [];
594
+ }
1535
595
 
1536
- private convertStreamingErrorToStructured(error: any): CopilotKitError {
1537
- // Determine a more helpful error message based on context
1538
- let helpfulMessage = generateHelpfulErrorMessage(error, "agent streaming connection");
1539
-
1540
- // For network-related errors, use CopilotKitLowLevelError to preserve the original error
1541
- if (
1542
- error?.message?.includes("fetch failed") ||
1543
- error?.message?.includes("ECONNREFUSED") ||
1544
- error?.message?.includes("ENOTFOUND") ||
1545
- error?.message?.includes("ETIMEDOUT") ||
1546
- error?.message?.includes("terminated") ||
1547
- error?.cause?.code === "UND_ERR_SOCKET" ||
1548
- error?.message?.includes("other side closed") ||
1549
- error?.code === "UND_ERR_SOCKET"
1550
- ) {
1551
- return new CopilotKitLowLevelError({
1552
- error: error instanceof Error ? error : new Error(String(error)),
1553
- url: "agent streaming connection",
1554
- message: helpfulMessage,
596
+ if (!createMCPClient) {
597
+ // Mirror legacy behavior: when servers are provided without a factory, treat as misconfiguration
598
+ throw new CopilotKitMisuseError({
599
+ message:
600
+ "MCP Integration Error: `mcpServers` were provided, but the `createMCPClient` function was not passed to the CopilotRuntime constructor. Please provide an implementation for `createMCPClient`.",
1555
601
  });
1556
602
  }
1557
603
 
1558
- // For all other errors, preserve the raw error in a basic CopilotKitError
1559
- return new CopilotKitError({
1560
- message: helpfulMessage,
1561
- code: CopilotKitErrorCode.UNKNOWN,
1562
- });
1563
- }
1564
-
1565
- private async error(
1566
- type: CopilotErrorEvent["type"],
1567
- context: CopilotRequestContext,
1568
- error?: any,
1569
- publicApiKey?: string,
1570
- ): Promise<void> {
1571
- if (!this.onError) return;
1572
-
1573
- // Just check if publicApiKey is defined (regardless of validity)
1574
- if (!publicApiKey) {
1575
- if (!this.hasWarnedAboutError) {
1576
- console.warn(
1577
- "CopilotKit: onError handler provided but requires publicApiKey to be defined for error handling to work.",
1578
- );
1579
- this.hasWarnedAboutError = true;
604
+ // Merge and dedupe endpoints by URL; request-level overrides take precedence
605
+ const effectiveEndpoints = (() => {
606
+ const byUrl = new Map<string, MCPEndpointConfig>();
607
+ for (const ep of runtimeMcpServers) {
608
+ if (ep?.endpoint) byUrl.set(ep.endpoint, ep);
609
+ }
610
+ for (const ep of requestMcpServers) {
611
+ if (ep?.endpoint) byUrl.set(ep.endpoint, ep);
612
+ }
613
+ return Array.from(byUrl.values());
614
+ })();
615
+
616
+ const allTools: BasicAgentConfiguration["tools"] = [];
617
+
618
+ for (const config of effectiveEndpoints) {
619
+ const endpointUrl = config.endpoint;
620
+ // Return cached tool definitions when available
621
+ const cached = this.mcpToolsCache.get(endpointUrl);
622
+ if (cached) {
623
+ allTools.push(...cached);
624
+ continue;
1580
625
  }
1581
- return;
1582
- }
1583
626
 
1584
- try {
1585
- const errorEvent: CopilotErrorEvent = {
1586
- type,
1587
- timestamp: Date.now(),
1588
- context,
1589
- ...(error && { error }),
1590
- };
627
+ try {
628
+ const client = await createMCPClient(config);
629
+ const toolsMap = await client.tools();
630
+
631
+ const toolDefs: BasicAgentConfiguration["tools"] = Object.entries(toolsMap).map(
632
+ ([toolName, tool]: [string, MCPTool]) => {
633
+ const params: Parameter[] = extractParametersFromSchema(tool);
634
+ const zodSchema = getZodParameters(params);
635
+ return {
636
+ name: toolName,
637
+ description: tool.description || `MCP tool: ${toolName} (from ${endpointUrl})`,
638
+ parameters: zodSchema,
639
+ };
640
+ },
641
+ );
1591
642
 
1592
- await this.onError(errorEvent);
1593
- } catch (errorHandlerError) {
1594
- // Don't let error handler errors break the main flow
1595
- console.error("Error in onError handler:", errorHandlerError);
643
+ // Cache per endpoint and add to aggregate
644
+ this.mcpToolsCache.set(endpointUrl, toolDefs);
645
+ allTools.push(...toolDefs);
646
+ } catch (error) {
647
+ console.error(
648
+ `MCP: Failed to fetch tools from endpoint ${endpointUrl}. Skipping. Error:`,
649
+ error,
650
+ );
651
+ // Cache empty to prevent repeated attempts within lifecycle
652
+ this.mcpToolsCache.set(endpointUrl, []);
653
+ }
1596
654
  }
1597
- }
1598
-
1599
- /**
1600
- * Public method to handle GraphQL validation errors
1601
- * This allows the GraphQL resolver to send validation errors through the error system
1602
- */
1603
- public async errorGraphQLError(
1604
- error: { message: string; code: string; type: string },
1605
- context: {
1606
- operation: string;
1607
- cloudConfigPresent: boolean;
1608
- guardrailsEnabled: boolean;
1609
- },
1610
- ): Promise<void> {
1611
- if (!this.onError) return;
1612
655
 
1613
- try {
1614
- await this.onError({
1615
- type: "error",
1616
- timestamp: Date.now(),
1617
- context: {
1618
- source: "runtime",
1619
- request: {
1620
- operation: context.operation,
1621
- startTime: Date.now(),
1622
- },
1623
- technical: {
1624
- environment: process.env.NODE_ENV,
1625
- },
1626
- metadata: {
1627
- errorType: "GraphQLValidationError",
1628
- cloudConfigPresent: context.cloudConfigPresent,
1629
- guardrailsEnabled: context.guardrailsEnabled,
1630
- },
1631
- },
1632
- error,
1633
- });
1634
- } catch (errorHandlerError) {
1635
- // Don't let error handler errors break the main flow
1636
- console.error("Error in onError handler:", errorHandlerError);
656
+ // Dedupe tools by name while preserving last-in wins (request overrides)
657
+ const dedupedByName = new Map<string, (typeof allTools)[number]>();
658
+ for (const tool of allTools) {
659
+ dedupedByName.set(tool.name, tool);
1637
660
  }
1638
- }
1639
- }
1640
661
 
1641
- export function flattenToolCallsNoDuplicates(toolsByPriority: ActionInput[]): ActionInput[] {
1642
- let allTools: ActionInput[] = [];
1643
- const allToolNames: string[] = [];
1644
- for (const tool of toolsByPriority) {
1645
- if (!allToolNames.includes(tool.name)) {
1646
- allTools.push(tool);
1647
- allToolNames.push(tool.name);
1648
- }
662
+ return Array.from(dedupedByName.values());
1649
663
  }
1650
- return allTools;
1651
664
  }
1652
665
 
1653
666
  // The two functions below are "factory functions", meant to create the action objects that adhere to the expected interfaces