@copilotkit/runtime 1.9.2-next.2 → 1.9.2-next.20

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 (87) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/dist/{chunk-6TQCQ3WJ.mjs → chunk-6KWGISZM.mjs} +2 -2
  3. package/dist/chunk-AMUJQ6IR.mjs +50 -0
  4. package/dist/chunk-AMUJQ6IR.mjs.map +1 -0
  5. package/dist/{chunk-CD2SZGIZ.mjs → chunk-GJ2JWK4N.mjs} +2 -2
  6. package/dist/{chunk-DOWRU5U6.mjs → chunk-IKR6ULGF.mjs} +2353 -1854
  7. package/dist/chunk-IKR6ULGF.mjs.map +1 -0
  8. package/dist/{chunk-IIXJVVTV.mjs → chunk-QLLV2QVK.mjs} +132 -78
  9. package/dist/chunk-QLLV2QVK.mjs.map +1 -0
  10. package/dist/{chunk-D3SPXEVJ.mjs → chunk-S77YPKO6.mjs} +2 -2
  11. package/dist/{chunk-5BIEM2UU.mjs → chunk-XWBDEXDA.mjs} +4 -3
  12. package/dist/{chunk-5BIEM2UU.mjs.map → chunk-XWBDEXDA.mjs.map} +1 -1
  13. package/dist/{chunk-ODF35LFG.mjs → chunk-ZJZTEQVW.mjs} +19 -2
  14. package/dist/chunk-ZJZTEQVW.mjs.map +1 -0
  15. package/dist/{groq-adapter-25a2bd35.d.ts → groq-adapter-098f97f6.d.ts} +5 -1
  16. package/dist/index.d.ts +4 -3
  17. package/dist/index.js +3728 -3114
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.mjs +12 -8
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/lib/index.d.ts +5 -4
  22. package/dist/lib/index.js +2711 -2140
  23. package/dist/lib/index.js.map +1 -1
  24. package/dist/lib/index.mjs +9 -8
  25. package/dist/lib/integrations/index.d.ts +3 -3
  26. package/dist/lib/integrations/index.js +160 -96
  27. package/dist/lib/integrations/index.js.map +1 -1
  28. package/dist/lib/integrations/index.mjs +7 -6
  29. package/dist/lib/integrations/nest/index.d.ts +2 -2
  30. package/dist/lib/integrations/nest/index.js +160 -96
  31. package/dist/lib/integrations/nest/index.js.map +1 -1
  32. package/dist/lib/integrations/nest/index.mjs +5 -4
  33. package/dist/lib/integrations/node-express/index.d.ts +2 -2
  34. package/dist/lib/integrations/node-express/index.js +160 -96
  35. package/dist/lib/integrations/node-express/index.js.map +1 -1
  36. package/dist/lib/integrations/node-express/index.mjs +5 -4
  37. package/dist/lib/integrations/node-http/index.d.ts +2 -2
  38. package/dist/lib/integrations/node-http/index.js +160 -96
  39. package/dist/lib/integrations/node-http/index.js.map +1 -1
  40. package/dist/lib/integrations/node-http/index.mjs +4 -3
  41. package/dist/service-adapters/index.d.ts +6 -4
  42. package/dist/service-adapters/index.js +202 -107
  43. package/dist/service-adapters/index.js.map +1 -1
  44. package/dist/service-adapters/index.mjs +6 -2
  45. package/dist/service-adapters/shared/index.d.ts +9 -0
  46. package/dist/service-adapters/shared/index.js +72 -0
  47. package/dist/service-adapters/shared/index.js.map +1 -0
  48. package/dist/service-adapters/shared/index.mjs +8 -0
  49. package/dist/service-adapters/shared/index.mjs.map +1 -0
  50. package/dist/{shared-e272b15a.d.ts → shared-41d4988d.d.ts} +45 -5
  51. package/dist/utils/index.d.ts +17 -1
  52. package/dist/utils/index.js +3 -2
  53. package/dist/utils/index.js.map +1 -1
  54. package/dist/utils/index.mjs +1 -1
  55. package/package.json +2 -2
  56. package/src/agents/langgraph/event-source.ts +36 -38
  57. package/src/agents/langgraph/events.ts +19 -1
  58. package/src/graphql/resolvers/copilot.resolver.ts +107 -45
  59. package/src/graphql/resolvers/state.resolver.ts +3 -3
  60. package/src/lib/error-messages.ts +200 -0
  61. package/src/lib/integrations/shared.ts +43 -0
  62. package/src/lib/runtime/__tests__/copilot-runtime-error.test.ts +169 -0
  63. package/src/lib/runtime/agui-action.ts +9 -3
  64. package/src/lib/runtime/copilot-runtime.ts +384 -83
  65. package/src/lib/runtime/langgraph/langgraph-agent.ts +12 -0
  66. package/src/lib/runtime/remote-action-constructors.ts +28 -3
  67. package/src/lib/runtime/remote-actions.ts +6 -0
  68. package/src/lib/runtime/remote-lg-action.ts +130 -40
  69. package/src/lib/streaming.ts +125 -36
  70. package/src/service-adapters/anthropic/anthropic-adapter.ts +67 -8
  71. package/src/service-adapters/anthropic/utils.ts +3 -8
  72. package/src/service-adapters/events.ts +37 -81
  73. package/src/service-adapters/google/google-genai-adapter.ts +5 -0
  74. package/src/service-adapters/groq/groq-adapter.ts +66 -56
  75. package/src/service-adapters/index.ts +1 -0
  76. package/src/service-adapters/openai/openai-adapter.ts +4 -3
  77. package/src/service-adapters/shared/error-utils.ts +61 -0
  78. package/src/service-adapters/shared/index.ts +1 -0
  79. package/src/utils/failed-response-status-reasons.ts +23 -1
  80. package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +172 -387
  81. package/dist/chunk-DOWRU5U6.mjs.map +0 -1
  82. package/dist/chunk-IIXJVVTV.mjs.map +0 -1
  83. package/dist/chunk-ODF35LFG.mjs.map +0 -1
  84. package/dist/{chunk-6TQCQ3WJ.mjs.map → chunk-6KWGISZM.mjs.map} +0 -0
  85. package/dist/{chunk-CD2SZGIZ.mjs.map → chunk-GJ2JWK4N.mjs.map} +0 -0
  86. package/dist/{chunk-D3SPXEVJ.mjs.map → chunk-S77YPKO6.mjs.map} +0 -0
  87. package/dist/{langserve-4a5c9217.d.ts → langserve-fc5cac89.d.ts} +7 -7
@@ -22,12 +22,16 @@ export function constructAGUIRemoteAction({
22
22
  agentStates,
23
23
  agent,
24
24
  metaEvents,
25
+ threadMetadata,
26
+ nodeName,
25
27
  }: {
26
28
  logger: Logger;
27
29
  messages: Message[];
28
30
  agentStates?: AgentStateInput[];
29
31
  agent: AbstractAgent;
30
32
  metaEvents?: MetaEventInput[];
33
+ threadMetadata?: Record<string, any>;
34
+ nodeName?: string;
31
35
  }) {
32
36
  const action = {
33
37
  name: agent.agentId,
@@ -67,9 +71,11 @@ export function constructAGUIRemoteAction({
67
71
  };
68
72
  });
69
73
 
70
- const forwardedProps = metaEvents.length
71
- ? { command: { resume: metaEvents[0]?.response } }
72
- : undefined;
74
+ const forwardedProps = {
75
+ ...(metaEvents?.length ? { command: { resume: metaEvents[0]?.response } } : {}),
76
+ ...(threadMetadata ? { threadMetadata } : {}),
77
+ ...(nodeName ? { nodeName } : {}),
78
+ };
73
79
 
74
80
  return agent.legacy_to_be_removed_runAgentBridged({
75
81
  tools,
@@ -20,11 +20,14 @@ import {
20
20
  CopilotKitApiDiscoveryError,
21
21
  randomId,
22
22
  CopilotKitError,
23
- CopilotKitRemoteEndpointDiscoveryError,
24
23
  CopilotKitAgentDiscoveryError,
25
24
  CopilotKitMisuseError,
26
25
  CopilotKitErrorCode,
27
26
  CopilotKitLowLevelError,
27
+ CopilotErrorHandler,
28
+ CopilotErrorEvent,
29
+ CopilotRequestContext,
30
+ ensureStructuredError,
28
31
  } from "@copilotkit/shared";
29
32
  import {
30
33
  CopilotServiceAdapter,
@@ -81,10 +84,13 @@ import {
81
84
  convertMCPToolsToActions,
82
85
  generateMcpToolInstructions,
83
86
  } from "./mcp-tools-utils";
87
+ import { LangGraphAgent } from "./langgraph/langgraph-agent";
84
88
  // Define the function type alias here or import if defined elsewhere
85
89
  type CreateMCPClientFunction = (config: MCPEndpointConfig) => Promise<MCPClient>;
86
90
  // --- MCP Imports ---
87
91
 
92
+ import { generateHelpfulErrorMessage } from "../streaming";
93
+
88
94
  export interface CopilotRuntimeRequest {
89
95
  serviceAdapter: CopilotServiceAdapter;
90
96
  messages: MessageInput[];
@@ -274,6 +280,25 @@ export interface CopilotRuntimeConstructorParams<T extends Parameter[] | [] = []
274
280
  * ```
275
281
  */
276
282
  createMCPClient?: CreateMCPClientFunction;
283
+
284
+ /**
285
+ * Optional error handler for comprehensive debugging and observability.
286
+ *
287
+ * **Requires publicApiKey**: Error handling only works when requests include a valid publicApiKey.
288
+ * This is a premium CopilotKit Cloud feature.
289
+ *
290
+ * @param errorEvent - Structured error event with rich debugging context
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * const runtime = new CopilotRuntime({
295
+ * onError: (errorEvent) => {
296
+ * debugDashboard.capture(errorEvent);
297
+ * }
298
+ * });
299
+ * ```
300
+ */
301
+ onError?: CopilotErrorHandler;
277
302
  }
278
303
 
279
304
  export class CopilotRuntime<const T extends Parameter[] | [] = []> {
@@ -286,6 +311,8 @@ export class CopilotRuntime<const T extends Parameter[] | [] = []> {
286
311
  private delegateAgentProcessingToServiceAdapter: boolean;
287
312
  private observability?: CopilotObservabilityConfig;
288
313
  private availableAgents: Pick<AgentWithEndpoint, "name" | "id">[];
314
+ private onError?: CopilotErrorHandler;
315
+ private hasWarnedAboutError = false;
289
316
 
290
317
  // +++ MCP Properties +++
291
318
  private readonly mcpServersConfig?: MCPEndpointConfig[];
@@ -303,7 +330,21 @@ export class CopilotRuntime<const T extends Parameter[] | [] = []> {
303
330
  params?.remoteEndpoints.some((e) => e.type === EndpointType.LangGraphPlatform)
304
331
  ) {
305
332
  console.warn("Actions set in runtime instance will not be available for the agent");
333
+ console.warn(
334
+ `LangGraph Platform remote endpoints are deprecated in favor of the "agents" property`,
335
+ );
306
336
  }
337
+
338
+ // TODO: finalize
339
+ // if (
340
+ // params?.agents &&
341
+ // Object.values(params.agents).some((agent) => {
342
+ // return agent instanceof AguiLangGraphAgent && !(agent instanceof LangGraphAgent);
343
+ // })
344
+ // ) {
345
+ // console.warn('LangGraph Agent class should be imported from @copilotkit/runtime. ')
346
+ // }
347
+
307
348
  this.actions = params?.actions || [];
308
349
  this.availableAgents = [];
309
350
 
@@ -320,6 +361,7 @@ export class CopilotRuntime<const T extends Parameter[] | [] = []> {
320
361
  params?.delegateAgentProcessingToServiceAdapter || false;
321
362
  this.observability = params?.observability_c;
322
363
  this.agents = params?.agents ?? {};
364
+ this.onError = params?.onError;
323
365
  // +++ MCP Initialization +++
324
366
  this.mcpServersConfig = params?.mcpServers;
325
367
  this.createMCPClientImpl = params?.createMCPClient;
@@ -452,6 +494,32 @@ export class CopilotRuntime<const T extends Parameter[] | [] = []> {
452
494
  // For storing streamed chunks if progressive logging is enabled
453
495
  const streamedChunks: any[] = [];
454
496
 
497
+ // Track request start
498
+ await this.error(
499
+ "request",
500
+ {
501
+ threadId,
502
+ runId,
503
+ source: "runtime",
504
+ request: {
505
+ operation: "processRuntimeRequest",
506
+ method: "POST",
507
+ url: url,
508
+ startTime: requestStartTime,
509
+ },
510
+ agent: agentSession ? { name: agentSession.agentName } : undefined,
511
+ messages: {
512
+ input: rawMessages,
513
+ messageCount: rawMessages.length,
514
+ },
515
+ technical: {
516
+ environment: process.env.NODE_ENV,
517
+ },
518
+ },
519
+ undefined,
520
+ publicApiKey,
521
+ );
522
+
455
523
  try {
456
524
  if (
457
525
  Object.keys(this.agents).length &&
@@ -675,17 +743,60 @@ please use an LLM adapter instead.`,
675
743
  }
676
744
  }
677
745
 
746
+ let structuredError: CopilotKitError;
747
+
678
748
  if (error instanceof CopilotKitError) {
679
- throw error;
749
+ structuredError = error;
750
+ } else {
751
+ // Convert non-CopilotKitErrors to structured errors, but preserve already structured ones
752
+ structuredError = ensureStructuredError(error, (err) =>
753
+ this.convertStreamingErrorToStructured(err),
754
+ );
680
755
  }
681
756
 
682
- // Convert non-CopilotKitErrors to structured errors
683
- console.error("Error getting response:", error);
684
- const structuredError = this.convertStreamingErrorToStructured(error);
757
+ // Track the error
758
+ await this.error(
759
+ "error",
760
+ {
761
+ threadId,
762
+ runId,
763
+ source: "runtime",
764
+ request: {
765
+ operation: "processRuntimeRequest",
766
+ method: "POST",
767
+ url: url,
768
+ startTime: requestStartTime,
769
+ },
770
+ response: {
771
+ endTime: Date.now(),
772
+ latency: Date.now() - requestStartTime,
773
+ },
774
+ agent: agentSession ? { name: agentSession.agentName } : undefined,
775
+ technical: {
776
+ environment: process.env.NODE_ENV,
777
+ stackTrace: error instanceof Error ? error.stack : undefined,
778
+ },
779
+ },
780
+ structuredError,
781
+ publicApiKey,
782
+ );
783
+
685
784
  throw structuredError;
686
785
  }
687
786
  }
688
787
 
788
+ async getAllAgents(graphqlContext: GraphQLContext): Promise<(AgentWithEndpoint | Agent)[]> {
789
+ const agentsWithEndpoints = await this.discoverAgentsFromEndpoints(graphqlContext);
790
+ const aguiAgents = this.discoverAgentsFromAgui();
791
+
792
+ this.availableAgents = [...agentsWithEndpoints, ...aguiAgents].map((a) => ({
793
+ name: a.name,
794
+ id: a.id,
795
+ }));
796
+
797
+ return [...agentsWithEndpoints, ...aguiAgents];
798
+ }
799
+
689
800
  async discoverAgentsFromEndpoints(graphqlContext: GraphQLContext): Promise<AgentWithEndpoint[]> {
690
801
  const agents: Promise<AgentWithEndpoint[]> = this.remoteEndpointDefinitions.reduce(
691
802
  async (acc: Promise<Agent[]>, endpoint) => {
@@ -767,60 +878,35 @@ please use an LLM adapter instead.`,
767
878
  },
768
879
  Promise.resolve([]),
769
880
  );
770
- this.availableAgents = ((await agents) ?? []).map((a) => ({ name: a.name, id: a.id }));
771
881
 
772
882
  return agents;
773
883
  }
774
884
 
885
+ discoverAgentsFromAgui(): Agent[] {
886
+ return Object.entries(this.agents ?? []).map(([key, agent]: [string, AbstractAgent]) => ({
887
+ name: (agent as any).agentName ?? key,
888
+ id: agent.agentId ?? key,
889
+ description: agent.description ?? "",
890
+ }));
891
+ }
892
+
775
893
  async loadAgentState(
776
894
  graphqlContext: GraphQLContext,
777
895
  threadId: string,
778
896
  agentName: string,
779
897
  ): Promise<LoadAgentStateResponse> {
780
- const agentsWithEndpoints = await this.discoverAgentsFromEndpoints(graphqlContext);
898
+ const agents = await this.getAllAgents(graphqlContext);
781
899
 
782
- const agentWithEndpoint = agentsWithEndpoints.find((agent) => agent.name === agentName);
783
- if (!agentWithEndpoint) {
900
+ const agent = agents.find((agent) => agent.name === agentName);
901
+ if (!agent) {
784
902
  throw new Error("Agent not found");
785
903
  }
786
904
 
787
- if (agentWithEndpoint.endpoint.type === EndpointType.LangGraphPlatform) {
788
- const propertyHeaders = graphqlContext.properties.authorization
789
- ? { authorization: `Bearer ${graphqlContext.properties.authorization}` }
790
- : null;
791
-
792
- const client = new LangGraphClient({
793
- apiUrl: agentWithEndpoint.endpoint.deploymentUrl,
794
- apiKey: agentWithEndpoint.endpoint.langsmithApiKey,
795
- defaultHeaders: { ...propertyHeaders },
796
- });
797
- let state: any = {};
798
- try {
799
- state = (await client.threads.getState(threadId)).values as any;
800
- } catch (error) {}
801
-
802
- if (Object.keys(state).length === 0) {
803
- return {
804
- threadId: threadId || "",
805
- threadExists: false,
806
- state: JSON.stringify({}),
807
- messages: JSON.stringify([]),
808
- };
809
- } else {
810
- const { messages, ...stateWithoutMessages } = state;
811
- const copilotkitMessages = langchainMessagesToCopilotKit(messages);
812
- return {
813
- threadId: threadId || "",
814
- threadExists: true,
815
- state: JSON.stringify(stateWithoutMessages),
816
- messages: JSON.stringify(copilotkitMessages),
817
- };
818
- }
819
- } else if (
820
- agentWithEndpoint.endpoint.type === EndpointType.CopilotKit ||
821
- !("type" in agentWithEndpoint.endpoint)
905
+ if (
906
+ "endpoint" in agent &&
907
+ (agent.endpoint.type === EndpointType.CopilotKit || !("type" in agent.endpoint))
822
908
  ) {
823
- const cpkEndpoint = agentWithEndpoint.endpoint as CopilotKitEndpoint;
909
+ const cpkEndpoint = agent.endpoint as CopilotKitEndpoint;
824
910
  const fetchUrl = `${cpkEndpoint.url}/agents/state`;
825
911
  try {
826
912
  const response = await fetchWithRetry(fetchUrl, {
@@ -836,10 +922,24 @@ please use an LLM adapter instead.`,
836
922
  if (response.status === 404) {
837
923
  throw new CopilotKitApiDiscoveryError({ url: fetchUrl });
838
924
  }
925
+
926
+ // Extract semantic error information from response body
927
+ let errorMessage = `HTTP ${response.status} error`;
928
+ try {
929
+ const errorBody = await response.text();
930
+ const parsedError = JSON.parse(errorBody);
931
+ if (parsedError.error && typeof parsedError.error === "string") {
932
+ errorMessage = parsedError.error;
933
+ }
934
+ } catch {
935
+ // If parsing fails, fall back to generic message
936
+ }
937
+
839
938
  throw new ResolvedCopilotKitError({
840
939
  status: response.status,
841
940
  url: fetchUrl,
842
941
  isRemoteEndpoint: true,
942
+ message: errorMessage,
843
943
  });
844
944
  }
845
945
 
@@ -856,9 +956,65 @@ please use an LLM adapter instead.`,
856
956
  }
857
957
  throw new CopilotKitLowLevelError({ error, url: fetchUrl });
858
958
  }
959
+ }
960
+
961
+ const propertyHeaders = graphqlContext.properties.authorization
962
+ ? { authorization: `Bearer ${graphqlContext.properties.authorization}` }
963
+ : null;
964
+
965
+ let state: any = {};
966
+ try {
967
+ let client: LangGraphClient | null;
968
+ if ("endpoint" in agent && agent.endpoint.type === EndpointType.LangGraphPlatform) {
969
+ client = new LangGraphClient({
970
+ apiUrl: agent.endpoint.deploymentUrl,
971
+ apiKey: agent.endpoint.langsmithApiKey,
972
+ defaultHeaders: { ...propertyHeaders },
973
+ });
974
+ } else {
975
+ const aguiAgent = graphqlContext._copilotkit.runtime.agents[agent.name] as LangGraphAgent;
976
+ if (!aguiAgent) {
977
+ throw new Error(`Agent: ${agent.name} could not be resolved`);
978
+ }
979
+ // @ts-expect-error -- both clients are the same
980
+ client = aguiAgent.client ?? null;
981
+ }
982
+
983
+ state = client ? ((await client.threads.getState(threadId)).values as any) : {};
984
+ } catch (error) {
985
+ // All errors from agent state loading are user configuration issues
986
+ const errorMessage = error instanceof Error ? error.message : String(error);
987
+
988
+ // Log user configuration errors at debug level to reduce noise
989
+ console.debug(`Agent '${agentName}' configuration issue: ${errorMessage}`);
990
+
991
+ // Throw a configuration error - all agent state loading failures are user setup issues
992
+ throw new ResolvedCopilotKitError({
993
+ status: 400,
994
+ message: `Agent '${agentName}' failed to execute: ${errorMessage}`,
995
+ code: CopilotKitErrorCode.CONFIGURATION_ERROR,
996
+ });
997
+ }
998
+
999
+ if (Object.keys(state).length === 0) {
1000
+ return {
1001
+ threadId: threadId || "",
1002
+ threadExists: false,
1003
+ state: JSON.stringify({}),
1004
+ messages: JSON.stringify([]),
1005
+ };
859
1006
  } else {
860
- throw new Error(`Unknown endpoint type: ${(agentWithEndpoint.endpoint as any).type}`);
1007
+ const { messages, ...stateWithoutMessages } = state;
1008
+ const copilotkitMessages = langchainMessagesToCopilotKit(messages);
1009
+ return {
1010
+ threadId: threadId || "",
1011
+ threadExists: true,
1012
+ state: JSON.stringify(stateWithoutMessages),
1013
+ messages: JSON.stringify(copilotkitMessages),
1014
+ };
861
1015
  }
1016
+
1017
+ throw new Error(`Agent: ${agent.name} could not be resolved`);
862
1018
  }
863
1019
 
864
1020
  private async processAgentRequest(
@@ -884,6 +1040,33 @@ please use an LLM adapter instead.`,
884
1040
  // for backwards compatibility, deal with the case when no threadId is provided
885
1041
  const threadId = threadIdFromRequest ?? agentSession.threadId;
886
1042
 
1043
+ // Track agent request start
1044
+ await this.error(
1045
+ "agent_state",
1046
+ {
1047
+ threadId,
1048
+ source: "agent",
1049
+ request: {
1050
+ operation: "processAgentRequest",
1051
+ method: "POST",
1052
+ startTime: requestStartTime,
1053
+ },
1054
+ agent: {
1055
+ name: agentName,
1056
+ nodeName: nodeName,
1057
+ },
1058
+ messages: {
1059
+ input: rawMessages,
1060
+ messageCount: rawMessages.length,
1061
+ },
1062
+ technical: {
1063
+ environment: process.env.NODE_ENV,
1064
+ },
1065
+ },
1066
+ undefined,
1067
+ publicApiKey,
1068
+ );
1069
+
887
1070
  const serverSideActions = await this.getServerSideActions(request);
888
1071
 
889
1072
  const messages = convertGqlInputToMessages(rawMessages);
@@ -1011,9 +1194,7 @@ please use an LLM adapter instead.`,
1011
1194
  eventSource.stream(async (eventStream$) => {
1012
1195
  from(stream).subscribe({
1013
1196
  next: (event) => eventStream$.next(event),
1014
- error: (err) => {
1015
- console.error("Error in stream", err);
1016
-
1197
+ error: async (err) => {
1017
1198
  // Log error with observability if enabled
1018
1199
  if (this.observability?.enabled && publicApiKey) {
1019
1200
  try {
@@ -1035,8 +1216,39 @@ please use an LLM adapter instead.`,
1035
1216
  }
1036
1217
  }
1037
1218
 
1038
- // Convert network termination errors to structured errors
1039
- const structuredError = this.convertStreamingErrorToStructured(err);
1219
+ // Preserve structured CopilotKit errors, only convert unstructured errors
1220
+ const structuredError = ensureStructuredError(err, (error) =>
1221
+ this.convertStreamingErrorToStructured(error),
1222
+ );
1223
+
1224
+ // Track streaming errors
1225
+ await this.error(
1226
+ "error",
1227
+ {
1228
+ threadId,
1229
+ source: "agent",
1230
+ request: {
1231
+ operation: "processAgentRequest",
1232
+ method: "POST",
1233
+ startTime: requestStartTime,
1234
+ },
1235
+ response: {
1236
+ endTime: Date.now(),
1237
+ latency: Date.now() - requestStartTime,
1238
+ },
1239
+ agent: {
1240
+ name: agentName,
1241
+ nodeName: nodeName,
1242
+ },
1243
+ technical: {
1244
+ environment: process.env.NODE_ENV,
1245
+ stackTrace: err instanceof Error ? err.stack : undefined,
1246
+ },
1247
+ },
1248
+ structuredError,
1249
+ publicApiKey,
1250
+ );
1251
+
1040
1252
  eventStream$.error(structuredError);
1041
1253
  eventStream$.complete();
1042
1254
  },
@@ -1114,8 +1326,41 @@ please use an LLM adapter instead.`,
1114
1326
  }
1115
1327
  }
1116
1328
 
1329
+ // Ensure error is structured
1330
+ const structuredError = ensureStructuredError(error, (err) =>
1331
+ this.convertStreamingErrorToStructured(err),
1332
+ );
1333
+
1334
+ // Track the agent error
1335
+ await this.error(
1336
+ "error",
1337
+ {
1338
+ threadId,
1339
+ source: "agent",
1340
+ request: {
1341
+ operation: "processAgentRequest",
1342
+ method: "POST",
1343
+ startTime: requestStartTime,
1344
+ },
1345
+ response: {
1346
+ endTime: Date.now(),
1347
+ latency: Date.now() - requestStartTime,
1348
+ },
1349
+ agent: {
1350
+ name: agentName,
1351
+ nodeName: nodeName,
1352
+ },
1353
+ technical: {
1354
+ environment: process.env.NODE_ENV,
1355
+ stackTrace: error instanceof Error ? error.stack : undefined,
1356
+ },
1357
+ },
1358
+ structuredError,
1359
+ publicApiKey,
1360
+ );
1361
+
1117
1362
  console.error("Error getting response:", error);
1118
- throw error;
1363
+ throw structuredError;
1119
1364
  }
1120
1365
  }
1121
1366
 
@@ -1146,6 +1391,7 @@ please use an LLM adapter instead.`,
1146
1391
  frontendUrl: url,
1147
1392
  agents: this.agents,
1148
1393
  metaEvents: request.metaEvents,
1394
+ nodeName: request.agentSession?.nodeName,
1149
1395
  });
1150
1396
 
1151
1397
  const configuredActions =
@@ -1231,53 +1477,108 @@ please use an LLM adapter instead.`,
1231
1477
  }
1232
1478
 
1233
1479
  private convertStreamingErrorToStructured(error: any): CopilotKitError {
1234
- // Handle network termination errors
1235
- if (
1236
- error?.message?.includes("terminated") ||
1237
- error?.cause?.code === "UND_ERR_SOCKET" ||
1238
- error?.message?.includes("other side closed") ||
1239
- error?.code === "UND_ERR_SOCKET"
1240
- ) {
1241
- return new CopilotKitError({
1242
- message:
1243
- "Connection to agent was unexpectedly terminated. This may be due to the agent service being restarted or network issues. Please try again.",
1244
- code: CopilotKitErrorCode.NETWORK_ERROR,
1245
- });
1246
- }
1480
+ // Determine a more helpful error message based on context
1481
+ let helpfulMessage = generateHelpfulErrorMessage(error, "agent streaming connection");
1247
1482
 
1248
- // Handle other network-related errors
1483
+ // For network-related errors, use CopilotKitLowLevelError to preserve the original error
1249
1484
  if (
1250
1485
  error?.message?.includes("fetch failed") ||
1251
1486
  error?.message?.includes("ECONNREFUSED") ||
1252
1487
  error?.message?.includes("ENOTFOUND") ||
1253
- error?.message?.includes("ETIMEDOUT")
1488
+ error?.message?.includes("ETIMEDOUT") ||
1489
+ error?.message?.includes("terminated") ||
1490
+ error?.cause?.code === "UND_ERR_SOCKET" ||
1491
+ error?.message?.includes("other side closed") ||
1492
+ error?.code === "UND_ERR_SOCKET"
1254
1493
  ) {
1255
1494
  return new CopilotKitLowLevelError({
1256
1495
  error: error instanceof Error ? error : new Error(String(error)),
1257
1496
  url: "agent streaming connection",
1258
- message:
1259
- "Network error occurred during agent streaming. Please check your connection and try again.",
1497
+ message: helpfulMessage,
1260
1498
  });
1261
1499
  }
1262
1500
 
1263
- // Handle abort/cancellation errors (these are usually normal)
1264
- if (
1265
- error?.message?.includes("aborted") ||
1266
- error?.message?.includes("canceled") ||
1267
- error?.message?.includes("signal is aborted")
1268
- ) {
1269
- return new CopilotKitError({
1270
- message: "Agent request was cancelled",
1271
- code: CopilotKitErrorCode.UNKNOWN,
1272
- });
1273
- }
1274
-
1275
- // Default: convert unknown streaming errors
1501
+ // For all other errors, preserve the raw error in a basic CopilotKitError
1276
1502
  return new CopilotKitError({
1277
- message: `Agent streaming error: ${error?.message || String(error)}`,
1503
+ message: helpfulMessage,
1278
1504
  code: CopilotKitErrorCode.UNKNOWN,
1279
1505
  });
1280
1506
  }
1507
+
1508
+ private async error(
1509
+ type: CopilotErrorEvent["type"],
1510
+ context: CopilotRequestContext,
1511
+ error?: any,
1512
+ publicApiKey?: string,
1513
+ ): Promise<void> {
1514
+ if (!this.onError) return;
1515
+
1516
+ // Just check if publicApiKey is defined (regardless of validity)
1517
+ if (!publicApiKey) {
1518
+ if (!this.hasWarnedAboutError) {
1519
+ console.warn(
1520
+ "CopilotKit: onError handler provided but requires publicApiKey to be defined for error handling to work.",
1521
+ );
1522
+ this.hasWarnedAboutError = true;
1523
+ }
1524
+ return;
1525
+ }
1526
+
1527
+ try {
1528
+ const errorEvent: CopilotErrorEvent = {
1529
+ type,
1530
+ timestamp: Date.now(),
1531
+ context,
1532
+ ...(error && { error }),
1533
+ };
1534
+
1535
+ await this.onError(errorEvent);
1536
+ } catch (errorHandlerError) {
1537
+ // Don't let error handler errors break the main flow
1538
+ console.error("Error in onError handler:", errorHandlerError);
1539
+ }
1540
+ }
1541
+
1542
+ /**
1543
+ * Public method to handle GraphQL validation errors
1544
+ * This allows the GraphQL resolver to send validation errors through the error system
1545
+ */
1546
+ public async errorGraphQLError(
1547
+ error: { message: string; code: string; type: string },
1548
+ context: {
1549
+ operation: string;
1550
+ cloudConfigPresent: boolean;
1551
+ guardrailsEnabled: boolean;
1552
+ },
1553
+ ): Promise<void> {
1554
+ if (!this.onError) return;
1555
+
1556
+ try {
1557
+ await this.onError({
1558
+ type: "error",
1559
+ timestamp: Date.now(),
1560
+ context: {
1561
+ source: "runtime",
1562
+ request: {
1563
+ operation: context.operation,
1564
+ startTime: Date.now(),
1565
+ },
1566
+ technical: {
1567
+ environment: process.env.NODE_ENV,
1568
+ },
1569
+ metadata: {
1570
+ errorType: "GraphQLValidationError",
1571
+ cloudConfigPresent: context.cloudConfigPresent,
1572
+ guardrailsEnabled: context.guardrailsEnabled,
1573
+ },
1574
+ },
1575
+ error,
1576
+ });
1577
+ } catch (errorHandlerError) {
1578
+ // Don't let error handler errors break the main flow
1579
+ console.error("Error in onError handler:", errorHandlerError);
1580
+ }
1581
+ }
1281
1582
  }
1282
1583
 
1283
1584
  export function flattenToolCallsNoDuplicates(toolsByPriority: ActionInput[]): ActionInput[] {
@@ -16,6 +16,7 @@ import {
16
16
  LangGraphAgent as AGUILangGraphAgent,
17
17
  type LangGraphAgentConfig,
18
18
  ProcessedEvents,
19
+ SchemaKeys,
19
20
  } from "@ag-ui/langgraph";
20
21
  import { Message as LangGraphMessage } from "@langchain/langgraph-sdk/dist/types.messages";
21
22
 
@@ -180,6 +181,7 @@ export class LangGraphAgent extends AGUILangGraphAgent {
180
181
  messages,
181
182
  tools,
182
183
  );
184
+
183
185
  return {
184
186
  ...rest,
185
187
  copilotkit: {
@@ -187,4 +189,14 @@ export class LangGraphAgent extends AGUILangGraphAgent {
187
189
  },
188
190
  };
189
191
  }
192
+
193
+ async getSchemaKeys(): Promise<SchemaKeys> {
194
+ const CONSTANT_KEYS = ["copilotkit"];
195
+ const schemaKeys = await super.getSchemaKeys();
196
+ return {
197
+ config: schemaKeys.config,
198
+ input: schemaKeys.input ? [...schemaKeys.input, ...CONSTANT_KEYS] : null,
199
+ output: schemaKeys.output ? [...schemaKeys.output, ...CONSTANT_KEYS] : null,
200
+ };
201
+ }
190
202
  }