@copilotkit/runtime 0.0.0-fix-debug-infosys-20251107162427 → 0.0.0-fix-restore-handle-method-node-http-20251222114321

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