@copilotkit/runtime 1.9.2-next.8 → 1.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +177 -0
  2. package/dist/{chunk-4TLMVLU4.mjs → chunk-56ZNYBXV.mjs} +2 -2
  3. package/dist/chunk-AMUJQ6IR.mjs +50 -0
  4. package/dist/chunk-AMUJQ6IR.mjs.map +1 -0
  5. package/dist/{chunk-5SG4WWXH.mjs → chunk-GB4M7WUE.mjs} +2 -2
  6. package/dist/{chunk-JWPSIGSA.mjs → chunk-HJYWUUFY.mjs} +2 -2
  7. package/dist/{chunk-KYCDL2KX.mjs → chunk-M35WOOEP.mjs} +2 -2
  8. package/dist/{chunk-IIXJVVTV.mjs → chunk-QLLV2QVK.mjs} +132 -78
  9. package/dist/chunk-QLLV2QVK.mjs.map +1 -0
  10. package/dist/{chunk-WIXS6EG7.mjs → chunk-TE5QWP4H.mjs} +2401 -2055
  11. package/dist/chunk-TE5QWP4H.mjs.map +1 -0
  12. package/dist/{chunk-5BIEM2UU.mjs → chunk-XWBDEXDA.mjs} +4 -3
  13. package/dist/{chunk-5BIEM2UU.mjs.map → chunk-XWBDEXDA.mjs.map} +1 -1
  14. package/dist/{groq-adapter-25a2bd35.d.ts → groq-adapter-742818f2.d.ts} +5 -1
  15. package/dist/index.d.ts +4 -3
  16. package/dist/index.js +3747 -3303
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.mjs +12 -8
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/{langserve-4a5c9217.d.ts → langserve-3e8d0e06.d.ts} +13 -7
  21. package/dist/lib/index.d.ts +155 -5
  22. package/dist/lib/index.js +2808 -2407
  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 +151 -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 +151 -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 +151 -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 +151 -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-941d59dc.d.ts → shared-96b46379.d.ts} +23 -21
  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 +11 -11
  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 +85 -42
  59. package/src/lib/error-messages.ts +200 -0
  60. package/src/lib/integrations/shared.ts +43 -0
  61. package/src/lib/runtime/__tests__/{copilot-runtime-trace.test.ts → copilot-runtime-error.test.ts} +27 -27
  62. package/src/lib/runtime/__tests__/mcp-tools-utils.test.ts +464 -0
  63. package/src/lib/runtime/agui-action.ts +9 -3
  64. package/src/lib/runtime/copilot-runtime.ts +156 -160
  65. package/src/lib/runtime/mcp-tools-utils.ts +84 -18
  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 +85 -3
  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 +75 -80
  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-IIXJVVTV.mjs.map +0 -1
  82. package/dist/chunk-WIXS6EG7.mjs.map +0 -1
  83. /package/dist/{chunk-4TLMVLU4.mjs.map → chunk-56ZNYBXV.mjs.map} +0 -0
  84. /package/dist/{chunk-5SG4WWXH.mjs.map → chunk-GB4M7WUE.mjs.map} +0 -0
  85. /package/dist/{chunk-JWPSIGSA.mjs.map → chunk-HJYWUUFY.mjs.map} +0 -0
  86. /package/dist/{chunk-KYCDL2KX.mjs.map → chunk-M35WOOEP.mjs.map} +0 -0
@@ -14,7 +14,7 @@ export interface MCPTool {
14
14
  };
15
15
  };
16
16
  /** The function to call to execute the tool on the MCP server. */
17
- execute(options: { params: any }): Promise<any>;
17
+ execute(params: any): Promise<any>;
18
18
  }
19
19
 
20
20
  /**
@@ -51,7 +51,7 @@ export function extractParametersFromSchema(
51
51
  ? (toolOrSchema as MCPTool).schema
52
52
  : (toolOrSchema as MCPTool["schema"]);
53
53
 
54
- const toolParameters = schema?.parameters || schema?.parameters?.jsonSchema;
54
+ const toolParameters = schema?.parameters?.jsonSchema || schema?.parameters;
55
55
  const properties = toolParameters?.properties;
56
56
  const requiredParams = new Set(toolParameters?.required || []);
57
57
 
@@ -62,15 +62,45 @@ export function extractParametersFromSchema(
62
62
  for (const paramName in properties) {
63
63
  if (Object.prototype.hasOwnProperty.call(properties, paramName)) {
64
64
  const paramDef = properties[paramName];
65
+
66
+ // Enhanced type extraction with support for complex types
67
+ let type = paramDef.type || "string";
68
+ let description = paramDef.description || "";
69
+
70
+ // Handle arrays with items
71
+ if (type === "array" && paramDef.items) {
72
+ const itemType = paramDef.items.type || "object";
73
+ if (itemType === "object" && paramDef.items.properties) {
74
+ // For arrays of objects, describe the structure
75
+ const itemProperties = Object.keys(paramDef.items.properties).join(", ");
76
+ description =
77
+ description +
78
+ (description ? " " : "") +
79
+ `Array of objects with properties: ${itemProperties}`;
80
+ } else {
81
+ // For arrays of primitives
82
+ type = `array<${itemType}>`;
83
+ }
84
+ }
85
+
86
+ // Handle enums
87
+ if (paramDef.enum && Array.isArray(paramDef.enum)) {
88
+ const enumValues = paramDef.enum.join(" | ");
89
+ description = description + (description ? " " : "") + `Allowed values: ${enumValues}`;
90
+ }
91
+
92
+ // Handle objects with properties
93
+ if (type === "object" && paramDef.properties) {
94
+ const objectProperties = Object.keys(paramDef.properties).join(", ");
95
+ description =
96
+ description + (description ? " " : "") + `Object with properties: ${objectProperties}`;
97
+ }
98
+
65
99
  parameters.push({
66
100
  name: paramName,
67
- // Infer type, default to string. MCP schemas might have more complex types.
68
- // This might need refinement based on common MCP schema practices.
69
- type: paramDef.type || "string",
70
- description: paramDef.description,
101
+ type: type,
102
+ description: description,
71
103
  required: requiredParams.has(paramName),
72
- // Attributes might not directly map, handle if necessary
73
- // attributes: paramDef.attributes || undefined,
74
104
  });
75
105
  }
76
106
  }
@@ -95,7 +125,7 @@ export function convertMCPToolsToActions(
95
125
 
96
126
  const handler = async (params: any): Promise<any> => {
97
127
  try {
98
- const result = await tool.execute({ params });
128
+ const result = await tool.execute(params);
99
129
  // Ensure the result is a string or stringify it, as required by many LLMs.
100
130
  // This might need adjustment depending on how different LLMs handle tool results.
101
131
  return typeof result === "string" ? result : JSON.stringify(result);
@@ -148,16 +178,49 @@ export function generateMcpToolInstructions(toolsMap: Record<string, MCPTool>):
148
178
  if (tool.schema && typeof tool.schema === "object") {
149
179
  const schema = tool.schema as any;
150
180
 
151
- // Extract parameters from JSON Schema
152
- if (schema.properties) {
153
- const requiredParams = schema.required || [];
181
+ // Extract parameters from JSON Schema - check both schema.parameters.properties and schema.properties
182
+ const toolParameters = schema.parameters?.jsonSchema || schema.parameters;
183
+ const properties = toolParameters?.properties || schema.properties;
184
+ const requiredParams = toolParameters?.required || schema.required || [];
154
185
 
155
- // Build parameter documentation from properties
156
- const paramsList = Object.entries(schema.properties).map(([paramName, propSchema]) => {
186
+ if (properties) {
187
+ // Build parameter documentation from properties with enhanced type information
188
+ const paramsList = Object.entries(properties).map(([paramName, propSchema]) => {
157
189
  const propDetails = propSchema as any;
158
190
  const requiredMark = requiredParams.includes(paramName) ? "*" : "";
159
- const typeInfo = propDetails.type || "any";
160
- const description = propDetails.description ? ` - ${propDetails.description}` : "";
191
+ let typeInfo = propDetails.type || "any";
192
+ let description = propDetails.description ? ` - ${propDetails.description}` : "";
193
+
194
+ // Enhanced type display for complex schemas
195
+ if (typeInfo === "array" && propDetails.items) {
196
+ const itemType = propDetails.items.type || "object";
197
+ if (itemType === "object" && propDetails.items.properties) {
198
+ const itemProps = Object.keys(propDetails.items.properties).join(", ");
199
+ typeInfo = `array<object>`;
200
+ description =
201
+ description +
202
+ (description ? " " : " - ") +
203
+ `Array of objects with properties: ${itemProps}`;
204
+ } else {
205
+ typeInfo = `array<${itemType}>`;
206
+ }
207
+ }
208
+
209
+ // Handle enums
210
+ if (propDetails.enum && Array.isArray(propDetails.enum)) {
211
+ const enumValues = propDetails.enum.join(" | ");
212
+ description =
213
+ description + (description ? " " : " - ") + `Allowed values: ${enumValues}`;
214
+ }
215
+
216
+ // Handle objects
217
+ if (typeInfo === "object" && propDetails.properties) {
218
+ const objectProps = Object.keys(propDetails.properties).join(", ");
219
+ description =
220
+ description +
221
+ (description ? " " : " - ") +
222
+ `Object with properties: ${objectProps}`;
223
+ }
161
224
 
162
225
  return ` - ${paramName}${requiredMark} (${typeInfo})${description}`;
163
226
  });
@@ -183,6 +246,9 @@ ${toolsDoc}
183
246
  When using these tools:
184
247
  1. Only provide valid parameters according to their type requirements
185
248
  2. Required parameters are marked with *
186
- 3. Format API calls correctly with the expected parameter structure
187
- 4. Always check tool responses to determine your next action`;
249
+ 3. For array parameters, provide data in the correct array format
250
+ 4. For object parameters, include all required nested properties
251
+ 5. For enum parameters, use only the allowed values listed
252
+ 6. Format API calls correctly with the expected parameter structure
253
+ 7. Always check tool responses to determine your next action`;
188
254
  }
@@ -22,6 +22,9 @@ import { parseJson, tryMap } from "@copilotkit/shared";
22
22
  import { ActionInput } from "../../graphql/inputs/action.input";
23
23
  import { fetchWithRetry } from "./retry-utils";
24
24
 
25
+ // Import the utility function from remote-lg-action
26
+ import { isUserConfigurationError } from "./remote-lg-action";
27
+
25
28
  export function constructLGCRemoteAction({
26
29
  endpoint,
27
30
  graphqlContext,
@@ -93,11 +96,33 @@ export function constructLGCRemoteAction({
93
96
  writeJsonLineResponseToEventStream(response, eventSource.eventStream$);
94
97
  return eventSource.processLangGraphEvents();
95
98
  } catch (error) {
99
+ // Preserve structured CopilotKit errors with semantic information
100
+ if (error instanceof CopilotKitError || error instanceof CopilotKitLowLevelError) {
101
+ // Distinguish between user errors and system errors for logging
102
+ if (isUserConfigurationError(error)) {
103
+ logger.debug(
104
+ { url: endpoint.deploymentUrl, error: error.message, code: error.code },
105
+ "User configuration error in LangGraph Platform agent",
106
+ );
107
+ } else {
108
+ logger.error(
109
+ { url: endpoint.deploymentUrl, error: error.message, type: error.constructor.name },
110
+ "LangGraph Platform agent error",
111
+ );
112
+ }
113
+ throw error; // Re-throw the structured error to preserve semantic information
114
+ }
115
+
116
+ // For other errors, log and wrap them
96
117
  logger.error(
97
118
  { url: endpoint.deploymentUrl, status: 500, body: error.message },
98
119
  "Failed to execute LangGraph Platform agent",
99
120
  );
100
- throw new Error("Failed to execute LangGraph Platform agent");
121
+ throw new CopilotKitLowLevelError({
122
+ error: error instanceof Error ? error : new Error(String(error)),
123
+ url: endpoint.deploymentUrl,
124
+ message: "Failed to execute LangGraph Platform agent",
125
+ });
101
126
  }
102
127
  },
103
128
  }));
@@ -180,7 +205,7 @@ export function constructRemoteActions({
180
205
  logger.debug({ actionName: action.name, result }, "Executed remote action");
181
206
  return result;
182
207
  } catch (error) {
183
- if (error instanceof CopilotKitError) {
208
+ if (error instanceof CopilotKitError || error instanceof CopilotKitLowLevelError) {
184
209
  throw error;
185
210
  }
186
211
  throw new CopilotKitLowLevelError({ error, url: fetchUrl });
@@ -275,7 +300,7 @@ export function constructRemoteActions({
275
300
  throw new Error("Unsupported agent type");
276
301
  }
277
302
  } catch (error) {
278
- if (error instanceof CopilotKitError) {
303
+ if (error instanceof CopilotKitError || error instanceof CopilotKitLowLevelError) {
279
304
  throw error;
280
305
  }
281
306
  throw new CopilotKitLowLevelError({ error, url: fetchUrl });
@@ -131,6 +131,7 @@ export async function setupRemoteActions({
131
131
  frontendUrl,
132
132
  agents,
133
133
  metaEvents,
134
+ nodeName,
134
135
  }: {
135
136
  remoteEndpointDefinitions: EndpointDefinition[];
136
137
  graphqlContext: GraphQLContext;
@@ -139,10 +140,13 @@ export async function setupRemoteActions({
139
140
  frontendUrl?: string;
140
141
  agents: Record<string, AbstractAgent>;
141
142
  metaEvents?: MetaEventInput[];
143
+ nodeName?: string;
142
144
  }): Promise<Action[]> {
143
145
  const logger = graphqlContext.logger.child({ component: "remote-actions.fetchRemoteActions" });
144
146
  logger.debug({ remoteEndpointDefinitions }, "Fetching from remote endpoints");
145
147
 
148
+ const threadMetadata = (graphqlContext.properties?.threadMetadata as Record<string, any>) || {};
149
+
146
150
  // Remove duplicates of remoteEndpointDefinitions.url
147
151
  const filtered = remoteEndpointDefinitions.filter((value, index, self) => {
148
152
  if (value.type === EndpointType.LangGraphPlatform) {
@@ -204,6 +208,8 @@ export async function setupRemoteActions({
204
208
  agentStates,
205
209
  agent: agent,
206
210
  metaEvents,
211
+ threadMetadata,
212
+ nodeName,
207
213
  }),
208
214
  );
209
215
  }
@@ -19,9 +19,28 @@ import { CustomEventNames, LangGraphEventTypes } from "../../agents/langgraph/ev
19
19
  import telemetry from "../telemetry-client";
20
20
  import { MetaEventInput } from "../../graphql/inputs/meta-event.input";
21
21
  import { MetaEventName } from "../../graphql/types/meta-events.type";
22
- import { parseJson, CopilotKitMisuseError } from "@copilotkit/shared";
22
+ import {
23
+ parseJson,
24
+ CopilotKitMisuseError,
25
+ CopilotKitLowLevelError,
26
+ CopilotKitError,
27
+ } from "@copilotkit/shared";
23
28
  import { RemoveMessage } from "@langchain/core/messages";
24
29
  import { RETRY_CONFIG, isRetryableError, sleep, calculateDelay } from "./retry-utils";
30
+ import { generateHelpfulErrorMessage } from "../streaming";
31
+
32
+ // Utility to determine if an error is a user configuration issue vs system error
33
+ export function isUserConfigurationError(error: any): boolean {
34
+ return (
35
+ (error instanceof CopilotKitError || error instanceof CopilotKitLowLevelError) &&
36
+ (error.code === "NETWORK_ERROR" ||
37
+ error.code === "AUTHENTICATION_ERROR" ||
38
+ error.statusCode === 401 ||
39
+ error.statusCode === 403 ||
40
+ error.message?.toLowerCase().includes("authentication") ||
41
+ error.message?.toLowerCase().includes("api key"))
42
+ );
43
+ }
25
44
 
26
45
  type State = Record<string, any>;
27
46
 
@@ -128,6 +147,15 @@ export async function execute(args: ExecutionArgs): Promise<ReadableStream<Uint8
128
147
  See more: https://docs.copilotkit.ai/troubleshooting/common-issues`,
129
148
  });
130
149
  } else {
150
+ // Preserve already structured CopilotKit errors with semantic information
151
+ if (
152
+ lastError instanceof CopilotKitError ||
153
+ lastError instanceof CopilotKitLowLevelError ||
154
+ (lastError instanceof Error && lastError.name && lastError.name.includes("CopilotKit"))
155
+ ) {
156
+ throw lastError; // Re-throw to preserve semantic information and visibility settings
157
+ }
158
+
131
159
  throw new CopilotKitMisuseError({
132
160
  message: `
133
161
  The LangGraph client threw unhandled error ${lastError}.
@@ -341,7 +369,43 @@ async function streamEvents(controller: ReadableStreamDefaultController, args: E
341
369
  if (!["events", "values", "error", "updates"].includes(streamResponseChunk.event)) continue;
342
370
 
343
371
  if (streamResponseChunk.event === "error") {
344
- throw new Error(`Error event thrown: ${streamResponseChunk.data.message}`);
372
+ const errorData = streamResponseChunk.data;
373
+
374
+ // Check if this is a structured error from our Python agent
375
+ if (errorData && typeof errorData === "object" && "error_details" in errorData) {
376
+ const errorDetails = (errorData as any).error_details;
377
+
378
+ // Create a structured error with preserved semantic information
379
+ const preservedError = new CopilotKitLowLevelError({
380
+ error: new Error(errorDetails.message),
381
+ url: "langgraph platform agent",
382
+ message: `${errorDetails.type}: ${errorDetails.message}`,
383
+ });
384
+
385
+ // Add additional error context
386
+ if (errorDetails.status_code) {
387
+ (preservedError as any).statusCode = errorDetails.status_code;
388
+ }
389
+ if (errorDetails.response_data) {
390
+ (preservedError as any).responseData = errorDetails.response_data;
391
+ }
392
+ (preservedError as any).agentName = errorDetails.agent_name;
393
+ (preservedError as any).originalErrorType = errorDetails.type;
394
+
395
+ throw preservedError;
396
+ }
397
+
398
+ // Fallback for generic error messages
399
+ const helpfulMessage = generateHelpfulErrorMessage(
400
+ new Error(errorData.message),
401
+ "LangGraph Platform agent",
402
+ );
403
+
404
+ throw new CopilotKitLowLevelError({
405
+ error: new Error(errorData.message),
406
+ url: "langgraph platform agent",
407
+ message: helpfulMessage,
408
+ });
345
409
  }
346
410
 
347
411
  // Force event type, as data is not properly defined on the LG side.
@@ -507,11 +571,29 @@ async function streamEvents(controller: ReadableStreamDefaultController, args: E
507
571
 
508
572
  return Promise.resolve();
509
573
  } catch (e) {
510
- logger.error(e);
574
+ // Distinguish between user errors and system errors for logging
575
+ if (isUserConfigurationError(e)) {
576
+ // Log user errors at debug level to reduce noise
577
+ logger.debug({ error: e.message, code: e.code }, "User configuration error");
578
+ } else {
579
+ // Log actual system errors at error level
580
+ logger.error(e);
581
+ }
582
+
511
583
  telemetry.capture("oss.runtime.agent_execution_stream_errored", {
512
584
  ...streamInfo,
513
585
  error: e.message,
514
586
  });
587
+
588
+ // Re-throw CopilotKit errors so they can be handled properly at higher levels
589
+ if (
590
+ e instanceof CopilotKitError ||
591
+ e instanceof CopilotKitLowLevelError ||
592
+ (e instanceof Error && e.name && e.name.includes("CopilotKit"))
593
+ ) {
594
+ throw e;
595
+ }
596
+
515
597
  return Promise.resolve();
516
598
  }
517
599
  }
@@ -1,5 +1,11 @@
1
1
  import { ReplaySubject } from "rxjs";
2
- import { CopilotKitLowLevelError, CopilotKitError, CopilotKitErrorCode } from "@copilotkit/shared";
2
+ import {
3
+ CopilotKitLowLevelError,
4
+ CopilotKitError,
5
+ CopilotKitErrorCode,
6
+ ensureStructuredError,
7
+ } from "@copilotkit/shared";
8
+ import { errorConfig, getFallbackMessage } from "./error-messages";
3
9
 
4
10
  export async function writeJsonLineResponseToEventStream<T>(
5
11
  response: ReadableStream<Uint8Array>,
@@ -52,10 +58,8 @@ export async function writeJsonLineResponseToEventStream<T>(
52
58
  }
53
59
  }
54
60
  } catch (error) {
55
- console.error("Error in stream", error);
56
-
57
- // Convert network termination errors to structured errors
58
- const structuredError = convertStreamingErrorToStructured(error);
61
+ // Preserve already structured CopilotKit errors, only convert unstructured errors
62
+ const structuredError = ensureStructuredError(error, convertStreamingErrorToStructured);
59
63
  eventStream$.error(structuredError);
60
64
  return;
61
65
  }
@@ -63,50 +67,135 @@ export async function writeJsonLineResponseToEventStream<T>(
63
67
  }
64
68
 
65
69
  function convertStreamingErrorToStructured(error: any): CopilotKitError {
66
- // Handle network termination errors
67
- if (
68
- error?.message?.includes("terminated") ||
69
- error?.cause?.code === "UND_ERR_SOCKET" ||
70
- error?.message?.includes("other side closed") ||
71
- error?.code === "UND_ERR_SOCKET"
72
- ) {
73
- return new CopilotKitError({
74
- message:
75
- "Connection to agent was unexpectedly terminated. This is likely due to the agent service being down or experiencing issues. Please check your agent logs and try again.",
76
- code: CopilotKitErrorCode.NETWORK_ERROR,
77
- });
78
- }
70
+ // Determine a more helpful error message based on context
71
+ let helpfulMessage = generateHelpfulErrorMessage(error);
79
72
 
80
- // Handle other network-related errors
73
+ // For network-related errors, use CopilotKitLowLevelError to preserve the original error
81
74
  if (
82
75
  error?.message?.includes("fetch failed") ||
83
76
  error?.message?.includes("ECONNREFUSED") ||
84
77
  error?.message?.includes("ENOTFOUND") ||
85
- error?.message?.includes("ETIMEDOUT")
78
+ error?.message?.includes("ETIMEDOUT") ||
79
+ error?.message?.includes("terminated") ||
80
+ error?.cause?.code === "UND_ERR_SOCKET" ||
81
+ error?.message?.includes("other side closed") ||
82
+ error?.code === "UND_ERR_SOCKET"
86
83
  ) {
87
84
  return new CopilotKitLowLevelError({
88
85
  error: error instanceof Error ? error : new Error(String(error)),
89
86
  url: "streaming connection",
90
- message:
91
- "Network error occurred during streaming. Please check your connection and try again.",
92
- });
93
- }
94
-
95
- // Handle abort/cancellation errors (these are usually normal)
96
- if (
97
- error?.message?.includes("aborted") ||
98
- error?.message?.includes("canceled") ||
99
- error?.message?.includes("signal is aborted")
100
- ) {
101
- return new CopilotKitError({
102
- message: "Request was cancelled",
103
- code: CopilotKitErrorCode.UNKNOWN,
87
+ message: helpfulMessage,
104
88
  });
105
89
  }
106
90
 
107
- // Default: convert unknown streaming errors
91
+ // For all other errors, preserve the raw error in a basic CopilotKitError
108
92
  return new CopilotKitError({
109
- message: `Streaming error: ${error?.message || String(error)}`,
93
+ message: helpfulMessage,
110
94
  code: CopilotKitErrorCode.UNKNOWN,
111
95
  });
112
96
  }
97
+
98
+ /**
99
+ * Generates a helpful error message based on error patterns and context
100
+ */
101
+ export function generateHelpfulErrorMessage(error: any, context: string = "connection"): string {
102
+ const baseMessage = error?.message || String(error);
103
+
104
+ // Check for preserved error information from Python agent
105
+ const originalErrorType = error?.originalErrorType || error?.extensions?.originalErrorType;
106
+ const statusCode = error?.statusCode || error?.extensions?.statusCode;
107
+ const responseData = error?.responseData || error?.extensions?.responseData;
108
+
109
+ // First, try to match by original error type if available (more specific)
110
+ if (originalErrorType) {
111
+ const typeConfig = errorConfig.errorPatterns[originalErrorType];
112
+ if (typeConfig) {
113
+ return typeConfig.message.replace("{context}", context);
114
+ }
115
+ }
116
+
117
+ // Check for specific error patterns from configuration
118
+ for (const [pattern, config] of Object.entries(errorConfig.errorPatterns)) {
119
+ const shouldMatch =
120
+ baseMessage?.includes(pattern) ||
121
+ error?.cause?.code === pattern ||
122
+ error?.code === pattern ||
123
+ statusCode === parseInt(pattern) ||
124
+ (pattern === "other_side_closed" && baseMessage?.includes("other side closed")) ||
125
+ (pattern === "fetch_failed" && baseMessage?.includes("fetch failed")) ||
126
+ (responseData && JSON.stringify(responseData).includes(pattern));
127
+
128
+ if (shouldMatch) {
129
+ // Replace {context} placeholder with actual context
130
+ return config.message.replace("{context}", context);
131
+ }
132
+ }
133
+
134
+ // Try to match by category for fallback messages
135
+ if (isNetworkError(error)) {
136
+ return getFallbackMessage("network");
137
+ }
138
+
139
+ if (isConnectionError(error)) {
140
+ return getFallbackMessage("connection");
141
+ }
142
+
143
+ if (isAuthenticationError(error)) {
144
+ return getFallbackMessage("authentication");
145
+ }
146
+
147
+ // Default fallback
148
+ return getFallbackMessage("default");
149
+ }
150
+
151
+ /**
152
+ * Determines if an error is network-related
153
+ */
154
+ function isNetworkError(error: any): boolean {
155
+ const networkPatterns = ["ECONNREFUSED", "ENOTFOUND", "ETIMEDOUT", "fetch_failed"];
156
+ return networkPatterns.some(
157
+ (pattern) =>
158
+ error?.message?.includes(pattern) ||
159
+ error?.cause?.code === pattern ||
160
+ error?.code === pattern,
161
+ );
162
+ }
163
+
164
+ /**
165
+ * Determines if an error is connection-related
166
+ */
167
+ function isConnectionError(error: any): boolean {
168
+ const connectionPatterns = ["terminated", "UND_ERR_SOCKET", "other side closed"];
169
+ return connectionPatterns.some(
170
+ (pattern) =>
171
+ error?.message?.includes(pattern) ||
172
+ error?.cause?.code === pattern ||
173
+ error?.code === pattern,
174
+ );
175
+ }
176
+
177
+ /**
178
+ * Determines if an error is authentication-related
179
+ */
180
+ function isAuthenticationError(error: any): boolean {
181
+ const authPatterns = [
182
+ "401",
183
+ "api key",
184
+ "unauthorized",
185
+ "authentication",
186
+ "AuthenticationError",
187
+ "PermissionDeniedError",
188
+ ];
189
+ const baseMessage = error?.message || String(error);
190
+ const originalErrorType = error?.originalErrorType || error?.extensions?.originalErrorType;
191
+ const statusCode = error?.statusCode || error?.extensions?.statusCode;
192
+
193
+ return authPatterns.some(
194
+ (pattern) =>
195
+ baseMessage?.toLowerCase().includes(pattern.toLowerCase()) ||
196
+ originalErrorType === pattern ||
197
+ statusCode === 401 ||
198
+ error?.status === 401 ||
199
+ error?.statusCode === 401,
200
+ );
201
+ }