@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.
- package/CHANGELOG.md +19 -0
- package/dist/{chunk-MKAQQQ7D.mjs → chunk-44FYLJJJ.mjs} +2 -2
- package/dist/{chunk-T3HIFVU2.mjs → chunk-ESXPDYNT.mjs} +2 -2
- package/dist/{chunk-32OC2CBT.mjs → chunk-O6KXX5R5.mjs} +2 -2
- package/dist/{chunk-YVZCIKZM.mjs → chunk-YMIOUUPV.mjs} +260 -61
- package/dist/chunk-YMIOUUPV.mjs.map +1 -0
- package/dist/{chunk-5CY2MEHT.mjs → chunk-YXL4PSJM.mjs} +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +305 -106
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5 -5
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +298 -99
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/index.mjs +5 -5
- package/dist/lib/integrations/index.d.ts +2 -2
- package/dist/lib/integrations/index.js +16 -7
- package/dist/lib/integrations/index.js.map +1 -1
- package/dist/lib/integrations/index.mjs +4 -4
- package/dist/lib/integrations/nest/index.d.ts +1 -1
- package/dist/lib/integrations/nest/index.js +16 -7
- package/dist/lib/integrations/nest/index.js.map +1 -1
- package/dist/lib/integrations/nest/index.mjs +2 -2
- package/dist/lib/integrations/node-express/index.d.ts +1 -1
- package/dist/lib/integrations/node-express/index.js +16 -7
- package/dist/lib/integrations/node-express/index.js.map +1 -1
- package/dist/lib/integrations/node-express/index.mjs +2 -2
- package/dist/lib/integrations/node-http/index.d.ts +1 -1
- package/dist/lib/integrations/node-http/index.js +16 -7
- package/dist/lib/integrations/node-http/index.js.map +1 -1
- package/dist/lib/integrations/node-http/index.mjs +1 -1
- package/dist/{shared-0c31d7c5.d.ts → shared-e272b15a.d.ts} +1 -0
- package/package.json +2 -2
- package/src/agents/langgraph/event-source.ts +10 -1
- package/src/graphql/resolvers/copilot.resolver.ts +13 -0
- package/src/graphql/resolvers/state.resolver.ts +5 -7
- package/src/lib/runtime/copilot-runtime.ts +62 -6
- package/src/lib/runtime/remote-action-constructors.ts +37 -28
- package/src/lib/runtime/remote-lg-action.ts +45 -24
- package/src/lib/runtime/retry-utils.ts +96 -0
- package/src/lib/streaming.ts +54 -1
- package/src/service-adapters/events.ts +101 -6
- package/dist/chunk-YVZCIKZM.mjs.map +0 -1
- /package/dist/{chunk-MKAQQQ7D.mjs.map → chunk-44FYLJJJ.mjs.map} +0 -0
- /package/dist/{chunk-T3HIFVU2.mjs.map → chunk-ESXPDYNT.mjs.map} +0 -0
- /package/dist/{chunk-32OC2CBT.mjs.map → chunk-O6KXX5R5.mjs.map} +0 -0
- /package/dist/{chunk-5CY2MEHT.mjs.map → chunk-YXL4PSJM.mjs.map} +0 -0
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
672
|
-
throw
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
+
}
|
package/src/lib/streaming.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|