@copilotkit/runtime 1.9.1-next.0 → 1.9.2-next.0

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 (47) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/{chunk-MKAQQQ7D.mjs → chunk-44FYLJJJ.mjs} +2 -2
  3. package/dist/{chunk-T3HIFVU2.mjs → chunk-ESXPDYNT.mjs} +2 -2
  4. package/dist/{chunk-32OC2CBT.mjs → chunk-O6KXX5R5.mjs} +2 -2
  5. package/dist/{chunk-YVZCIKZM.mjs → chunk-YMIOUUPV.mjs} +260 -61
  6. package/dist/chunk-YMIOUUPV.mjs.map +1 -0
  7. package/dist/{chunk-5CY2MEHT.mjs → chunk-YXL4PSJM.mjs} +2 -2
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.js +305 -106
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +5 -5
  12. package/dist/lib/index.d.ts +1 -1
  13. package/dist/lib/index.js +298 -99
  14. package/dist/lib/index.js.map +1 -1
  15. package/dist/lib/index.mjs +5 -5
  16. package/dist/lib/integrations/index.d.ts +2 -2
  17. package/dist/lib/integrations/index.js +16 -7
  18. package/dist/lib/integrations/index.js.map +1 -1
  19. package/dist/lib/integrations/index.mjs +4 -4
  20. package/dist/lib/integrations/nest/index.d.ts +1 -1
  21. package/dist/lib/integrations/nest/index.js +16 -7
  22. package/dist/lib/integrations/nest/index.js.map +1 -1
  23. package/dist/lib/integrations/nest/index.mjs +2 -2
  24. package/dist/lib/integrations/node-express/index.d.ts +1 -1
  25. package/dist/lib/integrations/node-express/index.js +16 -7
  26. package/dist/lib/integrations/node-express/index.js.map +1 -1
  27. package/dist/lib/integrations/node-express/index.mjs +2 -2
  28. package/dist/lib/integrations/node-http/index.d.ts +1 -1
  29. package/dist/lib/integrations/node-http/index.js +16 -7
  30. package/dist/lib/integrations/node-http/index.js.map +1 -1
  31. package/dist/lib/integrations/node-http/index.mjs +1 -1
  32. package/dist/{shared-0c31d7c5.d.ts → shared-e272b15a.d.ts} +1 -0
  33. package/package.json +2 -2
  34. package/src/agents/langgraph/event-source.ts +10 -1
  35. package/src/graphql/resolvers/copilot.resolver.ts +13 -0
  36. package/src/graphql/resolvers/state.resolver.ts +5 -7
  37. package/src/lib/runtime/copilot-runtime.ts +62 -6
  38. package/src/lib/runtime/remote-action-constructors.ts +37 -28
  39. package/src/lib/runtime/remote-lg-action.ts +45 -24
  40. package/src/lib/runtime/retry-utils.ts +96 -0
  41. package/src/lib/streaming.ts +54 -1
  42. package/src/service-adapters/events.ts +101 -6
  43. package/dist/chunk-YVZCIKZM.mjs.map +0 -1
  44. /package/dist/{chunk-MKAQQQ7D.mjs.map → chunk-44FYLJJJ.mjs.map} +0 -0
  45. /package/dist/{chunk-T3HIFVU2.mjs.map → chunk-ESXPDYNT.mjs.map} +0 -0
  46. /package/dist/{chunk-32OC2CBT.mjs.map → chunk-O6KXX5R5.mjs.map} +0 -0
  47. /package/dist/{chunk-5CY2MEHT.mjs.map → chunk-YXL4PSJM.mjs.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  copilotRuntimeNodeHttpEndpoint
3
- } from "../../../chunk-YVZCIKZM.mjs";
3
+ } from "../../../chunk-YMIOUUPV.mjs";
4
4
  import "../../../chunk-IIXJVVTV.mjs";
5
5
  import "../../../chunk-5BIEM2UU.mjs";
6
6
  import "../../../chunk-SHBDMA63.mjs";
@@ -360,6 +360,7 @@ declare class CopilotRuntime<const T extends Parameter[] | [] = []> {
360
360
  private processAgentRequest;
361
361
  private getServerSideActions;
362
362
  private detectProvider;
363
+ private convertStreamingErrorToStructured;
363
364
  }
364
365
  declare function flattenToolCallsNoDuplicates(toolsByPriority: ActionInput[]): ActionInput[];
365
366
  declare function copilotKitEndpoint(config: Omit<CopilotKitEndpoint, "type">): CopilotKitEndpoint;
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "publishConfig": {
10
10
  "access": "public"
11
11
  },
12
- "version": "1.9.1-next.0",
12
+ "version": "1.9.2-next.0",
13
13
  "sideEffects": false,
14
14
  "main": "./dist/index.js",
15
15
  "module": "./dist/index.mjs",
@@ -66,7 +66,7 @@
66
66
  "rxjs": "^7.8.1",
67
67
  "type-graphql": "2.0.0-rc.1",
68
68
  "zod": "^3.23.3",
69
- "@copilotkit/shared": "1.9.1-next.0"
69
+ "@copilotkit/shared": "1.9.2-next.0"
70
70
  },
71
71
  "peerDependencies": {
72
72
  "@ag-ui/client": ">=0.0.28",
@@ -5,7 +5,7 @@ import {
5
5
  RuntimeEventTypes,
6
6
  RuntimeMetaEventName,
7
7
  } from "../../service-adapters/events";
8
- import { randomId } from "@copilotkit/shared";
8
+ import { randomId, CopilotKitError } from "@copilotkit/shared";
9
9
 
10
10
  interface LangGraphEventWithState {
11
11
  event: LangGraphEvent | null;
@@ -284,6 +284,15 @@ export class RemoteLangGraphEventSource {
284
284
  }),
285
285
  catchError((error) => {
286
286
  console.error(error);
287
+
288
+ // If it's a structured CopilotKitError, re-throw it to be handled by the frontend error system
289
+ if (
290
+ error instanceof CopilotKitError ||
291
+ (error?.name && error.name.includes("CopilotKit"))
292
+ ) {
293
+ throw error;
294
+ }
295
+
287
296
  const events: RuntimeEvent[] = [];
288
297
 
289
298
  if (lastEventWithState?.lastMessageId && !lastEventWithState.isToolCall) {
@@ -54,6 +54,7 @@ import telemetry from "../../lib/telemetry-client";
54
54
  import { randomId } from "@copilotkit/shared";
55
55
  import { AgentsResponse } from "../types/agents-response.type";
56
56
  import { LangGraphEventTypes } from "../../agents/langgraph/events";
57
+ import { CopilotKitError } from "@copilotkit/shared";
57
58
 
58
59
  const invokeGuardrails = async ({
59
60
  baseUrl,
@@ -646,6 +647,18 @@ export class CopilotResolver {
646
647
  },
647
648
  error: (err) => {
648
649
  logger.error({ err }, "Error in event stream");
650
+
651
+ // If it's a structured CopilotKitError, stop the repeater with the error so frontend can handle it
652
+ if (
653
+ err instanceof CopilotKitError ||
654
+ (err instanceof Error && err.name && err.name.includes("CopilotKit"))
655
+ ) {
656
+ eventStreamSubscription?.unsubscribe();
657
+ rejectOutputMessagesPromise(err);
658
+ stopStreamingMessages(err); // Pass the error to stop the GraphQL stream with this error
659
+ return;
660
+ }
661
+
649
662
  responseStatus$.next(
650
663
  new UnknownErrorResponse({
651
664
  description: `An unknown error has occurred in the event stream`,
@@ -4,6 +4,7 @@ import { Query } from "type-graphql";
4
4
  import { LoadAgentStateResponse } from "../types/load-agent-state-response.type";
5
5
  import type { GraphQLContext } from "../../lib/integrations";
6
6
  import { LoadAgentStateInput } from "../inputs/load-agent-state.input";
7
+ import { CopilotKitAgentDiscoveryError } from "@copilotkit/shared";
7
8
 
8
9
  @Resolver(() => LoadAgentStateResponse)
9
10
  export class StateResolver {
@@ -11,14 +12,11 @@ export class StateResolver {
11
12
  async loadAgentState(@Ctx() ctx: GraphQLContext, @Arg("data") data: LoadAgentStateInput) {
12
13
  const agents = await ctx._copilotkit.runtime.discoverAgentsFromEndpoints(ctx);
13
14
  const agent = agents.find((agent) => agent.name === data.agentName);
14
-
15
15
  if (!agent) {
16
- return {
17
- threadId: data.threadId || "",
18
- threadExists: false,
19
- state: JSON.stringify({}),
20
- messages: JSON.stringify([]),
21
- };
16
+ throw new CopilotKitAgentDiscoveryError({
17
+ agentName: data.agentName,
18
+ availableAgents: agents.map((a) => ({ name: a.name, id: a.name })),
19
+ });
22
20
  }
23
21
 
24
22
  const state = await ctx._copilotkit.runtime.loadAgentState(ctx, data.threadId, data.agentName);
@@ -20,9 +20,11 @@ import {
20
20
  CopilotKitApiDiscoveryError,
21
21
  randomId,
22
22
  CopilotKitError,
23
- CopilotKitLowLevelError,
23
+ CopilotKitRemoteEndpointDiscoveryError,
24
24
  CopilotKitAgentDiscoveryError,
25
25
  CopilotKitMisuseError,
26
+ CopilotKitErrorCode,
27
+ CopilotKitLowLevelError,
26
28
  } from "@copilotkit/shared";
27
29
  import {
28
30
  CopilotServiceAdapter,
@@ -54,6 +56,7 @@ import { from } from "rxjs";
54
56
  import { AgentStateInput } from "../../graphql/inputs/agent-state.input";
55
57
  import { ActionInputAvailability } from "../../graphql/types/enums";
56
58
  import { createHeaders } from "./remote-action-constructors";
59
+ import { fetchWithRetry } from "./retry-utils";
57
60
  import { Agent } from "../../graphql/types/agents-response.type";
58
61
  import { ExtensionsInput } from "../../graphql/inputs/extensions.input";
59
62
  import { ExtensionsResponse } from "../../graphql/types/extensions-response.type";
@@ -667,9 +670,11 @@ please use an LLM adapter instead.`,
667
670
  if (error instanceof CopilotKitError) {
668
671
  throw error;
669
672
  }
673
+
674
+ // Convert non-CopilotKitErrors to structured errors
670
675
  console.error("Error getting response:", error);
671
- eventSource.sendErrorMessageToChat();
672
- throw error;
676
+ const structuredError = this.convertStreamingErrorToStructured(error);
677
+ throw structuredError;
673
678
  }
674
679
  }
675
680
 
@@ -721,7 +726,7 @@ please use an LLM adapter instead.`,
721
726
  const cpkEndpoint = endpoint as CopilotKitEndpoint;
722
727
  const fetchUrl = `${endpoint.url}/info`;
723
728
  try {
724
- const response = await fetch(fetchUrl, {
729
+ const response = await fetchWithRetry(fetchUrl, {
725
730
  method: "POST",
726
731
  headers: createHeaders(cpkEndpoint.onBeforeRequest, graphqlContext),
727
732
  body: JSON.stringify({ properties: graphqlContext.properties }),
@@ -810,7 +815,7 @@ please use an LLM adapter instead.`,
810
815
  const cpkEndpoint = agentWithEndpoint.endpoint as CopilotKitEndpoint;
811
816
  const fetchUrl = `${cpkEndpoint.url}/agents/state`;
812
817
  try {
813
- const response = await fetch(fetchUrl, {
818
+ const response = await fetchWithRetry(fetchUrl, {
814
819
  method: "POST",
815
820
  headers: createHeaders(cpkEndpoint.onBeforeRequest, graphqlContext),
816
821
  body: JSON.stringify({
@@ -1022,7 +1027,9 @@ please use an LLM adapter instead.`,
1022
1027
  }
1023
1028
  }
1024
1029
 
1025
- eventStream$.error(err);
1030
+ // Convert network termination errors to structured errors
1031
+ const structuredError = this.convertStreamingErrorToStructured(err);
1032
+ eventStream$.error(structuredError);
1026
1033
  eventStream$.complete();
1027
1034
  },
1028
1035
  complete: () => eventStream$.complete(),
@@ -1214,6 +1221,55 @@ please use an LLM adapter instead.`,
1214
1221
  if (adapterName.includes("LangChain")) return "langchain";
1215
1222
  return undefined;
1216
1223
  }
1224
+
1225
+ private convertStreamingErrorToStructured(error: any): CopilotKitError {
1226
+ // Handle network termination errors
1227
+ if (
1228
+ error?.message?.includes("terminated") ||
1229
+ error?.cause?.code === "UND_ERR_SOCKET" ||
1230
+ error?.message?.includes("other side closed") ||
1231
+ error?.code === "UND_ERR_SOCKET"
1232
+ ) {
1233
+ return new CopilotKitError({
1234
+ message:
1235
+ "Connection to agent was unexpectedly terminated. This may be due to the agent service being restarted or network issues. Please try again.",
1236
+ code: CopilotKitErrorCode.NETWORK_ERROR,
1237
+ });
1238
+ }
1239
+
1240
+ // Handle other network-related errors
1241
+ if (
1242
+ error?.message?.includes("fetch failed") ||
1243
+ error?.message?.includes("ECONNREFUSED") ||
1244
+ error?.message?.includes("ENOTFOUND") ||
1245
+ error?.message?.includes("ETIMEDOUT")
1246
+ ) {
1247
+ return new CopilotKitLowLevelError({
1248
+ error: error instanceof Error ? error : new Error(String(error)),
1249
+ url: "agent streaming connection",
1250
+ message:
1251
+ "Network error occurred during agent streaming. Please check your connection and try again.",
1252
+ });
1253
+ }
1254
+
1255
+ // Handle abort/cancellation errors (these are usually normal)
1256
+ if (
1257
+ error?.message?.includes("aborted") ||
1258
+ error?.message?.includes("canceled") ||
1259
+ error?.message?.includes("signal is aborted")
1260
+ ) {
1261
+ return new CopilotKitError({
1262
+ message: "Agent request was cancelled",
1263
+ code: CopilotKitErrorCode.UNKNOWN,
1264
+ });
1265
+ }
1266
+
1267
+ // Default: convert unknown streaming errors
1268
+ return new CopilotKitError({
1269
+ message: `Agent streaming error: ${error?.message || String(error)}`,
1270
+ code: CopilotKitErrorCode.UNKNOWN,
1271
+ });
1272
+ }
1217
1273
  }
1218
1274
 
1219
1275
  export function flattenToolCallsNoDuplicates(toolsByPriority: ActionInput[]): ActionInput[] {
@@ -20,6 +20,7 @@ import { writeJsonLineResponseToEventStream } from "../streaming";
20
20
  import { CopilotKitApiDiscoveryError, ResolvedCopilotKitError } from "@copilotkit/shared";
21
21
  import { parseJson, tryMap } from "@copilotkit/shared";
22
22
  import { ActionInput } from "../../graphql/inputs/action.input";
23
+ import { fetchWithRetry } from "./retry-utils";
23
24
 
24
25
  export function constructLGCRemoteAction({
25
26
  endpoint,
@@ -144,15 +145,19 @@ export function constructRemoteActions({
144
145
 
145
146
  const fetchUrl = `${url}/actions/execute`;
146
147
  try {
147
- const response = await fetch(fetchUrl, {
148
- method: "POST",
149
- headers,
150
- body: JSON.stringify({
151
- name: action.name,
152
- arguments: args,
153
- properties: graphqlContext.properties,
154
- }),
155
- });
148
+ const response = await fetchWithRetry(
149
+ fetchUrl,
150
+ {
151
+ method: "POST",
152
+ headers,
153
+ body: JSON.stringify({
154
+ name: action.name,
155
+ arguments: args,
156
+ properties: graphqlContext.properties,
157
+ }),
158
+ },
159
+ logger,
160
+ );
156
161
 
157
162
  if (!response.ok) {
158
163
  logger.error(
@@ -219,25 +224,29 @@ export function constructRemoteActions({
219
224
 
220
225
  const fetchUrl = `${url}/agents/execute`;
221
226
  try {
222
- const response = await fetch(fetchUrl, {
223
- method: "POST",
224
- headers,
225
- body: JSON.stringify({
226
- name,
227
- threadId,
228
- nodeName,
229
- messages: [...messages, ...additionalMessages],
230
- state,
231
- config,
232
- properties: graphqlContext.properties,
233
- actions: tryMap(actionInputsWithoutAgents, (action: ActionInput) => ({
234
- name: action.name,
235
- description: action.description,
236
- parameters: JSON.parse(action.jsonSchema),
237
- })),
238
- metaEvents,
239
- }),
240
- });
227
+ const response = await fetchWithRetry(
228
+ fetchUrl,
229
+ {
230
+ method: "POST",
231
+ headers,
232
+ body: JSON.stringify({
233
+ name,
234
+ threadId,
235
+ nodeName,
236
+ messages: [...messages, ...additionalMessages],
237
+ state,
238
+ config,
239
+ properties: graphqlContext.properties,
240
+ actions: tryMap(actionInputsWithoutAgents, (action: ActionInput) => ({
241
+ name: action.name,
242
+ description: action.description,
243
+ parameters: JSON.parse(action.jsonSchema),
244
+ })),
245
+ metaEvents,
246
+ }),
247
+ },
248
+ logger,
249
+ );
241
250
 
242
251
  if (!response.ok) {
243
252
  logger.error(
@@ -19,6 +19,7 @@ import { MetaEventInput } from "../../graphql/inputs/meta-event.input";
19
19
  import { MetaEventName } from "../../graphql/types/meta-events.type";
20
20
  import { parseJson, CopilotKitMisuseError } from "@copilotkit/shared";
21
21
  import { RemoveMessage } from "@langchain/core/messages";
22
+ import { RETRY_CONFIG, isRetryableError, sleep, calculateDelay } from "./retry-utils";
22
23
 
23
24
  type State = Record<string, any>;
24
25
 
@@ -88,32 +89,52 @@ let activeInterruptEvent = false;
88
89
  export async function execute(args: ExecutionArgs): Promise<ReadableStream<Uint8Array>> {
89
90
  return new ReadableStream({
90
91
  async start(controller) {
91
- try {
92
- await streamEvents(controller, args);
93
- controller.close();
94
- } catch (err) {
95
- // Unwrap the possible cause
96
- const cause = err?.cause;
97
-
98
- // Check code directly if it exists
99
- const errorCode = cause?.code || err?.code;
100
-
101
- if (errorCode === "ECONNREFUSED") {
102
- throw new CopilotKitMisuseError({
103
- message: `
104
- The LangGraph client could not connect to the graph. Please further check previous logs, which includes further details.
105
-
106
- See more: https://docs.copilotkit.ai/troubleshooting/common-issues`,
107
- });
108
- } else {
109
- throw new CopilotKitMisuseError({
110
- message: `
111
- The LangGraph client threw unhandled error ${err}.
112
-
113
- See more: https://docs.copilotkit.ai/troubleshooting/common-issues`,
114
- });
92
+ let lastError: any;
93
+
94
+ // Retry logic for transient connection errors
95
+ for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
96
+ try {
97
+ await streamEvents(controller, args);
98
+ controller.close();
99
+ return; // Success - exit retry loop
100
+ } catch (err) {
101
+ lastError = err;
102
+
103
+ // Check if this is a retryable error
104
+ if (isRetryableError(err) && attempt < RETRY_CONFIG.maxRetries) {
105
+ const delay = calculateDelay(attempt);
106
+ console.warn(
107
+ `LangGraph connection attempt ${attempt + 1}/${RETRY_CONFIG.maxRetries + 1} failed. ` +
108
+ `Retrying in ${delay}ms. Error: ${err?.message || String(err)}`,
109
+ );
110
+ await sleep(delay);
111
+ continue; // Retry
112
+ }
113
+
114
+ // Not retryable or max retries exceeded - handle error
115
+ break;
115
116
  }
116
117
  }
118
+
119
+ // Handle the final error after retries exhausted
120
+ const cause = lastError?.cause;
121
+ const errorCode = cause?.code || lastError?.code;
122
+
123
+ if (errorCode === "ECONNREFUSED") {
124
+ throw new CopilotKitMisuseError({
125
+ message: `
126
+ The LangGraph client could not connect to the graph after ${RETRY_CONFIG.maxRetries + 1} attempts. Please further check previous logs, which includes further details.
127
+
128
+ See more: https://docs.copilotkit.ai/troubleshooting/common-issues`,
129
+ });
130
+ } else {
131
+ throw new CopilotKitMisuseError({
132
+ message: `
133
+ The LangGraph client threw unhandled error ${lastError}.
134
+
135
+ See more: https://docs.copilotkit.ai/troubleshooting/common-issues`,
136
+ });
137
+ }
117
138
  },
118
139
  });
119
140
  }
@@ -0,0 +1,96 @@
1
+ import { Logger } from "pino";
2
+
3
+ // Retry configuration for network requests
4
+ export const RETRY_CONFIG = {
5
+ maxRetries: 3,
6
+ baseDelayMs: 1000,
7
+ maxDelayMs: 5000,
8
+ // HTTP status codes that should be retried
9
+ retryableStatusCodes: [502, 503, 504, 408, 429],
10
+ // Network error patterns that should be retried
11
+ retryableErrorMessages: [
12
+ "fetch failed",
13
+ "network error",
14
+ "connection timeout",
15
+ "ECONNREFUSED",
16
+ "ETIMEDOUT",
17
+ "ENOTFOUND",
18
+ "ECONNRESET",
19
+ ],
20
+ };
21
+
22
+ // Helper function to check if an error/response is retryable
23
+ export function isRetryableError(error: any, response?: Response): boolean {
24
+ // Check HTTP response status
25
+ if (response && RETRY_CONFIG.retryableStatusCodes.includes(response.status)) {
26
+ return true;
27
+ }
28
+
29
+ // Check error codes (for connection errors like ECONNREFUSED)
30
+ const errorCode = error?.cause?.code || error?.code;
31
+ if (errorCode && RETRY_CONFIG.retryableErrorMessages.includes(errorCode)) {
32
+ return true;
33
+ }
34
+
35
+ // Check error messages
36
+ const errorMessage = error?.message?.toLowerCase() || "";
37
+ return RETRY_CONFIG.retryableErrorMessages.some((msg) => errorMessage.includes(msg));
38
+ }
39
+
40
+ // Helper function to sleep for a given duration
41
+ export function sleep(ms: number): Promise<void> {
42
+ return new Promise((resolve) => setTimeout(resolve, ms));
43
+ }
44
+
45
+ // Calculate exponential backoff delay
46
+ export function calculateDelay(attempt: number): number {
47
+ const delay = RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt);
48
+ return Math.min(delay, RETRY_CONFIG.maxDelayMs);
49
+ }
50
+
51
+ // Retry wrapper for fetch requests
52
+ export async function fetchWithRetry(
53
+ url: string,
54
+ options: RequestInit,
55
+ logger?: Logger,
56
+ ): Promise<Response> {
57
+ let lastError: any;
58
+
59
+ for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
60
+ try {
61
+ const response = await fetch(url, options);
62
+
63
+ // If response is retryable, treat as error and retry
64
+ if (isRetryableError(null, response) && attempt < RETRY_CONFIG.maxRetries) {
65
+ const delay = calculateDelay(attempt);
66
+ logger?.warn(
67
+ `Request to ${url} failed with status ${response.status}. ` +
68
+ `Retrying attempt ${attempt + 1}/${RETRY_CONFIG.maxRetries + 1} in ${delay}ms.`,
69
+ );
70
+ await sleep(delay);
71
+ continue;
72
+ }
73
+
74
+ return response; // Success or non-retryable error
75
+ } catch (error) {
76
+ lastError = error;
77
+
78
+ // Check if this is a retryable network error
79
+ if (isRetryableError(error) && attempt < RETRY_CONFIG.maxRetries) {
80
+ const delay = calculateDelay(attempt);
81
+ logger?.warn(
82
+ `Request to ${url} failed with network error. ` +
83
+ `Retrying attempt ${attempt + 1}/${RETRY_CONFIG.maxRetries + 1} in ${delay}ms. Error: ${error?.message || String(error)}`,
84
+ );
85
+ await sleep(delay);
86
+ continue;
87
+ }
88
+
89
+ // Not retryable or max retries exceeded
90
+ break;
91
+ }
92
+ }
93
+
94
+ // Re-throw the last error after retries exhausted
95
+ throw lastError;
96
+ }
@@ -1,4 +1,5 @@
1
1
  import { ReplaySubject } from "rxjs";
2
+ import { CopilotKitLowLevelError, CopilotKitError, CopilotKitErrorCode } from "@copilotkit/shared";
2
3
 
3
4
  export async function writeJsonLineResponseToEventStream<T>(
4
5
  response: ReadableStream<Uint8Array>,
@@ -52,8 +53,60 @@ export async function writeJsonLineResponseToEventStream<T>(
52
53
  }
53
54
  } catch (error) {
54
55
  console.error("Error in stream", error);
55
- eventStream$.error(error);
56
+
57
+ // Convert network termination errors to structured errors
58
+ const structuredError = convertStreamingErrorToStructured(error);
59
+ eventStream$.error(structuredError);
56
60
  return;
57
61
  }
58
62
  eventStream$.complete();
59
63
  }
64
+
65
+ 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
+ }
79
+
80
+ // Handle other network-related errors
81
+ if (
82
+ error?.message?.includes("fetch failed") ||
83
+ error?.message?.includes("ECONNREFUSED") ||
84
+ error?.message?.includes("ENOTFOUND") ||
85
+ error?.message?.includes("ETIMEDOUT")
86
+ ) {
87
+ return new CopilotKitLowLevelError({
88
+ error: error instanceof Error ? error : new Error(String(error)),
89
+ 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,
104
+ });
105
+ }
106
+
107
+ // Default: convert unknown streaming errors
108
+ return new CopilotKitError({
109
+ message: `Streaming error: ${error?.message || String(error)}`,
110
+ code: CopilotKitErrorCode.UNKNOWN,
111
+ });
112
+ }