@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.
- package/CHANGELOG.md +177 -0
- package/dist/{chunk-4TLMVLU4.mjs → chunk-56ZNYBXV.mjs} +2 -2
- package/dist/chunk-AMUJQ6IR.mjs +50 -0
- package/dist/chunk-AMUJQ6IR.mjs.map +1 -0
- package/dist/{chunk-5SG4WWXH.mjs → chunk-GB4M7WUE.mjs} +2 -2
- package/dist/{chunk-JWPSIGSA.mjs → chunk-HJYWUUFY.mjs} +2 -2
- package/dist/{chunk-KYCDL2KX.mjs → chunk-M35WOOEP.mjs} +2 -2
- package/dist/{chunk-IIXJVVTV.mjs → chunk-QLLV2QVK.mjs} +132 -78
- package/dist/chunk-QLLV2QVK.mjs.map +1 -0
- package/dist/{chunk-WIXS6EG7.mjs → chunk-TE5QWP4H.mjs} +2401 -2055
- package/dist/chunk-TE5QWP4H.mjs.map +1 -0
- package/dist/{chunk-5BIEM2UU.mjs → chunk-XWBDEXDA.mjs} +4 -3
- package/dist/{chunk-5BIEM2UU.mjs.map → chunk-XWBDEXDA.mjs.map} +1 -1
- package/dist/{groq-adapter-25a2bd35.d.ts → groq-adapter-742818f2.d.ts} +5 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3747 -3303
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +12 -8
- package/dist/index.mjs.map +1 -1
- package/dist/{langserve-4a5c9217.d.ts → langserve-3e8d0e06.d.ts} +13 -7
- package/dist/lib/index.d.ts +155 -5
- package/dist/lib/index.js +2808 -2407
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/index.mjs +9 -8
- package/dist/lib/integrations/index.d.ts +3 -3
- package/dist/lib/integrations/index.js +151 -96
- package/dist/lib/integrations/index.js.map +1 -1
- package/dist/lib/integrations/index.mjs +7 -6
- package/dist/lib/integrations/nest/index.d.ts +2 -2
- package/dist/lib/integrations/nest/index.js +151 -96
- package/dist/lib/integrations/nest/index.js.map +1 -1
- package/dist/lib/integrations/nest/index.mjs +5 -4
- package/dist/lib/integrations/node-express/index.d.ts +2 -2
- package/dist/lib/integrations/node-express/index.js +151 -96
- package/dist/lib/integrations/node-express/index.js.map +1 -1
- package/dist/lib/integrations/node-express/index.mjs +5 -4
- package/dist/lib/integrations/node-http/index.d.ts +2 -2
- package/dist/lib/integrations/node-http/index.js +151 -96
- package/dist/lib/integrations/node-http/index.js.map +1 -1
- package/dist/lib/integrations/node-http/index.mjs +4 -3
- package/dist/service-adapters/index.d.ts +6 -4
- package/dist/service-adapters/index.js +202 -107
- package/dist/service-adapters/index.js.map +1 -1
- package/dist/service-adapters/index.mjs +6 -2
- package/dist/service-adapters/shared/index.d.ts +9 -0
- package/dist/service-adapters/shared/index.js +72 -0
- package/dist/service-adapters/shared/index.js.map +1 -0
- package/dist/service-adapters/shared/index.mjs +8 -0
- package/dist/service-adapters/shared/index.mjs.map +1 -0
- package/dist/{shared-941d59dc.d.ts → shared-96b46379.d.ts} +23 -21
- package/dist/utils/index.d.ts +17 -1
- package/dist/utils/index.js +3 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +1 -1
- package/package.json +11 -11
- package/src/agents/langgraph/event-source.ts +36 -38
- package/src/agents/langgraph/events.ts +19 -1
- package/src/graphql/resolvers/copilot.resolver.ts +85 -42
- package/src/lib/error-messages.ts +200 -0
- package/src/lib/integrations/shared.ts +43 -0
- package/src/lib/runtime/__tests__/{copilot-runtime-trace.test.ts → copilot-runtime-error.test.ts} +27 -27
- package/src/lib/runtime/__tests__/mcp-tools-utils.test.ts +464 -0
- package/src/lib/runtime/agui-action.ts +9 -3
- package/src/lib/runtime/copilot-runtime.ts +156 -160
- package/src/lib/runtime/mcp-tools-utils.ts +84 -18
- package/src/lib/runtime/remote-action-constructors.ts +28 -3
- package/src/lib/runtime/remote-actions.ts +6 -0
- package/src/lib/runtime/remote-lg-action.ts +85 -3
- package/src/lib/streaming.ts +125 -36
- package/src/service-adapters/anthropic/anthropic-adapter.ts +67 -8
- package/src/service-adapters/anthropic/utils.ts +3 -8
- package/src/service-adapters/events.ts +75 -80
- package/src/service-adapters/google/google-genai-adapter.ts +5 -0
- package/src/service-adapters/groq/groq-adapter.ts +66 -56
- package/src/service-adapters/index.ts +1 -0
- package/src/service-adapters/openai/openai-adapter.ts +4 -3
- package/src/service-adapters/shared/error-utils.ts +61 -0
- package/src/service-adapters/shared/index.ts +1 -0
- package/src/utils/failed-response-status-reasons.ts +23 -1
- package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +172 -387
- package/dist/chunk-IIXJVVTV.mjs.map +0 -1
- package/dist/chunk-WIXS6EG7.mjs.map +0 -1
- /package/dist/{chunk-4TLMVLU4.mjs.map → chunk-56ZNYBXV.mjs.map} +0 -0
- /package/dist/{chunk-5SG4WWXH.mjs.map → chunk-GB4M7WUE.mjs.map} +0 -0
- /package/dist/{chunk-JWPSIGSA.mjs.map → chunk-HJYWUUFY.mjs.map} +0 -0
- /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(
|
|
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
|
|
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
|
-
|
|
68
|
-
|
|
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(
|
|
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
|
-
|
|
153
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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.
|
|
187
|
-
4.
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/lib/streaming.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { ReplaySubject } from "rxjs";
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
-
//
|
|
67
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
91
|
+
// For all other errors, preserve the raw error in a basic CopilotKitError
|
|
108
92
|
return new CopilotKitError({
|
|
109
|
-
message:
|
|
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
|
+
}
|