@copilotkit/runtime 1.9.2-next.1 → 1.9.2-next.10
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 +66 -0
- package/dist/{chunk-YV3YXRMR.mjs → chunk-5OK4GLKL.mjs} +19 -2
- package/dist/chunk-5OK4GLKL.mjs.map +1 -0
- package/dist/{chunk-RIPQZJB5.mjs → chunk-6RUTA76W.mjs} +2 -2
- package/dist/chunk-AMUJQ6IR.mjs +50 -0
- package/dist/chunk-AMUJQ6IR.mjs.map +1 -0
- package/dist/{chunk-IIXJVVTV.mjs → chunk-GS7DO47Q.mjs} +155 -78
- package/dist/chunk-GS7DO47Q.mjs.map +1 -0
- package/dist/{chunk-XGBY45FP.mjs → chunk-PMIAGZGS.mjs} +2371 -1857
- package/dist/chunk-PMIAGZGS.mjs.map +1 -0
- package/dist/{chunk-C3SWOFLO.mjs → chunk-TOBFVWZU.mjs} +2 -2
- package/dist/{chunk-KPFOAXRX.mjs → chunk-VBXBFZEL.mjs} +2 -2
- 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-172a2ca4.d.ts} +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3748 -3096
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +12 -8
- package/dist/index.mjs.map +1 -1
- package/dist/lib/index.d.ts +7 -133
- package/dist/lib/index.js +3357 -2748
- 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 +162 -98
- 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 +162 -98
- 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 +162 -98
- 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 +162 -98
- 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 +225 -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-e272b15a.d.ts → shared-bd953ebf.d.ts} +45 -5
- 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 +4 -4
- package/src/agents/langgraph/event-source.ts +36 -38
- package/src/agents/langgraph/events.ts +19 -1
- package/src/graphql/resolvers/copilot.resolver.ts +108 -45
- package/src/graphql/resolvers/state.resolver.ts +3 -3
- 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 +169 -0
- package/src/lib/runtime/copilot-runtime.ts +412 -83
- package/src/lib/runtime/langgraph/langgraph-agent.ts +12 -0
- package/src/lib/runtime/remote-action-constructors.ts +28 -3
- package/src/lib/runtime/remote-lg-action.ts +130 -40
- 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 +37 -81
- 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 +18 -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-XGBY45FP.mjs.map +0 -1
- package/dist/chunk-YV3YXRMR.mjs.map +0 -1
- package/dist/{chunk-RIPQZJB5.mjs.map → chunk-6RUTA76W.mjs.map} +0 -0
- package/dist/{chunk-C3SWOFLO.mjs.map → chunk-TOBFVWZU.mjs.map} +0 -0
- package/dist/{chunk-KPFOAXRX.mjs.map → chunk-VBXBFZEL.mjs.map} +0 -0
- package/dist/{langserve-4a5c9217.d.ts → langserve-fc5cac89.d.ts} +7 -7
|
@@ -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 });
|
|
@@ -2,7 +2,9 @@ import {
|
|
|
2
2
|
Client as LangGraphClient,
|
|
3
3
|
EventsStreamEvent,
|
|
4
4
|
GraphSchema,
|
|
5
|
+
Interrupt,
|
|
5
6
|
StreamMode,
|
|
7
|
+
ThreadState,
|
|
6
8
|
} from "@langchain/langgraph-sdk";
|
|
7
9
|
import { createHash } from "node:crypto";
|
|
8
10
|
import { isValidUUID, randomUUID } from "@copilotkit/shared";
|
|
@@ -17,9 +19,28 @@ import { CustomEventNames, LangGraphEventTypes } from "../../agents/langgraph/ev
|
|
|
17
19
|
import telemetry from "../telemetry-client";
|
|
18
20
|
import { MetaEventInput } from "../../graphql/inputs/meta-event.input";
|
|
19
21
|
import { MetaEventName } from "../../graphql/types/meta-events.type";
|
|
20
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
parseJson,
|
|
24
|
+
CopilotKitMisuseError,
|
|
25
|
+
CopilotKitLowLevelError,
|
|
26
|
+
CopilotKitError,
|
|
27
|
+
} from "@copilotkit/shared";
|
|
21
28
|
import { RemoveMessage } from "@langchain/core/messages";
|
|
22
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
|
+
}
|
|
23
44
|
|
|
24
45
|
type State = Record<string, any>;
|
|
25
46
|
|
|
@@ -84,8 +105,6 @@ type SchemaKeys = {
|
|
|
84
105
|
config: string[] | null;
|
|
85
106
|
} | null;
|
|
86
107
|
|
|
87
|
-
let activeInterruptEvent = false;
|
|
88
|
-
|
|
89
108
|
export async function execute(args: ExecutionArgs): Promise<ReadableStream<Uint8Array>> {
|
|
90
109
|
return new ReadableStream({
|
|
91
110
|
async start(controller) {
|
|
@@ -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}.
|
|
@@ -188,7 +216,7 @@ async function streamEvents(controller: ReadableStreamDefaultController, args: E
|
|
|
188
216
|
await client.threads.create({ threadId });
|
|
189
217
|
}
|
|
190
218
|
|
|
191
|
-
let agentState = { values: {} };
|
|
219
|
+
let agentState = { values: {} } as ThreadState;
|
|
192
220
|
if (wasInitiatedWithExistingThread) {
|
|
193
221
|
agentState = await client.threads.getState(threadId);
|
|
194
222
|
}
|
|
@@ -218,16 +246,14 @@ async function streamEvents(controller: ReadableStreamDefaultController, args: E
|
|
|
218
246
|
const lgInterruptMetaEvent = metaEvents?.find(
|
|
219
247
|
(ev) => ev.name === MetaEventName.LangGraphInterruptEvent,
|
|
220
248
|
);
|
|
221
|
-
|
|
222
|
-
// state.messages includes only messages that were not processed by the agent, which are the interrupt messages
|
|
223
|
-
payload.command = { resume: state.messages };
|
|
224
|
-
}
|
|
249
|
+
|
|
225
250
|
if (lgInterruptMetaEvent?.response) {
|
|
226
251
|
let response = lgInterruptMetaEvent.response;
|
|
227
252
|
payload.command = { resume: parseJson(response, response) };
|
|
228
253
|
}
|
|
229
254
|
|
|
230
|
-
|
|
255
|
+
const interrupts = (agentState.tasks?.[0]?.interrupts ?? []) as Interrupt[];
|
|
256
|
+
if (mode === "continue" && !interrupts.length) {
|
|
231
257
|
await client.threads.updateState(threadId, { values: state, asNode: nodeName });
|
|
232
258
|
}
|
|
233
259
|
|
|
@@ -311,17 +337,30 @@ async function streamEvents(controller: ReadableStreamDefaultController, args: E
|
|
|
311
337
|
let shouldExit = false;
|
|
312
338
|
let externalRunId = null;
|
|
313
339
|
|
|
314
|
-
const streamResponse = client.runs.stream(threadId, assistantId, payload);
|
|
315
|
-
|
|
316
340
|
const emit = (message: string) => controller.enqueue(new TextEncoder().encode(message));
|
|
317
341
|
|
|
342
|
+
// If there are still outstanding unresolved interrupts, we must force resolution of them before moving forward
|
|
343
|
+
if (interrupts?.length && !payload.command?.resume) {
|
|
344
|
+
// If the interrupt is "by message" we assume the upcoming user message is a resoluton for the interrupt
|
|
345
|
+
if (!lgInterruptMetaEvent) {
|
|
346
|
+
// state.messages includes only messages that were not processed by the agent, which are the interrupt messages
|
|
347
|
+
payload.command = { resume: state.messages };
|
|
348
|
+
} else {
|
|
349
|
+
interrupts.forEach((interrupt) => {
|
|
350
|
+
emitInterrupt(interrupt.value, emit);
|
|
351
|
+
});
|
|
352
|
+
return Promise.resolve();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const streamResponse = client.runs.stream(threadId, assistantId, payload);
|
|
357
|
+
|
|
318
358
|
let latestStateValues = {};
|
|
319
359
|
let updatedState = state;
|
|
320
360
|
// If a manual emittance happens, it is the ultimate source of truth of state, unless a node has exited.
|
|
321
361
|
// Therefore, this value should either hold null, or the only edition of state that should be used.
|
|
322
362
|
let manuallyEmittedState = null;
|
|
323
363
|
|
|
324
|
-
activeInterruptEvent = false;
|
|
325
364
|
try {
|
|
326
365
|
telemetry.capture("oss.runtime.agent_execution_stream_started", {
|
|
327
366
|
hashedLgcKey: streamInfo.hashedLgcKey,
|
|
@@ -330,7 +369,43 @@ async function streamEvents(controller: ReadableStreamDefaultController, args: E
|
|
|
330
369
|
if (!["events", "values", "error", "updates"].includes(streamResponseChunk.event)) continue;
|
|
331
370
|
|
|
332
371
|
if (streamResponseChunk.event === "error") {
|
|
333
|
-
|
|
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
|
+
});
|
|
334
409
|
}
|
|
335
410
|
|
|
336
411
|
// Force event type, as data is not properly defined on the LG side.
|
|
@@ -345,33 +420,8 @@ async function streamEvents(controller: ReadableStreamDefaultController, args: E
|
|
|
345
420
|
|
|
346
421
|
const interruptEvents = chunk.data.__interrupt__;
|
|
347
422
|
if (interruptEvents?.length) {
|
|
348
|
-
activeInterruptEvent = true;
|
|
349
423
|
const interruptValue = interruptEvents?.[0].value;
|
|
350
|
-
|
|
351
|
-
typeof interruptValue != "string" &&
|
|
352
|
-
"__copilotkit_interrupt_value__" in interruptValue
|
|
353
|
-
) {
|
|
354
|
-
const evValue = interruptValue.__copilotkit_interrupt_value__;
|
|
355
|
-
emit(
|
|
356
|
-
JSON.stringify({
|
|
357
|
-
event: LangGraphEventTypes.OnCopilotKitInterrupt,
|
|
358
|
-
data: {
|
|
359
|
-
value: typeof evValue === "string" ? evValue : JSON.stringify(evValue),
|
|
360
|
-
messages: langchainMessagesToCopilotKit(interruptValue.__copilotkit_messages__),
|
|
361
|
-
},
|
|
362
|
-
}) + "\n",
|
|
363
|
-
);
|
|
364
|
-
} else {
|
|
365
|
-
emit(
|
|
366
|
-
JSON.stringify({
|
|
367
|
-
event: LangGraphEventTypes.OnInterrupt,
|
|
368
|
-
value:
|
|
369
|
-
typeof interruptValue === "string"
|
|
370
|
-
? interruptValue
|
|
371
|
-
: JSON.stringify(interruptValue),
|
|
372
|
-
}) + "\n",
|
|
373
|
-
);
|
|
374
|
-
}
|
|
424
|
+
emitInterrupt(interruptValue, emit);
|
|
375
425
|
continue;
|
|
376
426
|
}
|
|
377
427
|
if (streamResponseChunk.event === "updates") continue;
|
|
@@ -521,11 +571,29 @@ async function streamEvents(controller: ReadableStreamDefaultController, args: E
|
|
|
521
571
|
|
|
522
572
|
return Promise.resolve();
|
|
523
573
|
} catch (e) {
|
|
524
|
-
|
|
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
|
+
|
|
525
583
|
telemetry.capture("oss.runtime.agent_execution_stream_errored", {
|
|
526
584
|
...streamInfo,
|
|
527
585
|
error: e.message,
|
|
528
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
|
+
|
|
529
597
|
return Promise.resolve();
|
|
530
598
|
}
|
|
531
599
|
}
|
|
@@ -909,3 +977,25 @@ function getSchemaKeys(graphSchema: GraphSchema): SchemaKeys {
|
|
|
909
977
|
function filterObjectBySchemaKeys(obj: Record<string, any>, schemaKeys: string[]) {
|
|
910
978
|
return Object.fromEntries(Object.entries(obj).filter(([key]) => schemaKeys.includes(key)));
|
|
911
979
|
}
|
|
980
|
+
|
|
981
|
+
function emitInterrupt(interruptValue: any, emit: (data: string) => void) {
|
|
982
|
+
if (typeof interruptValue != "string" && "__copilotkit_interrupt_value__" in interruptValue) {
|
|
983
|
+
const evValue = interruptValue.__copilotkit_interrupt_value__;
|
|
984
|
+
emit(
|
|
985
|
+
JSON.stringify({
|
|
986
|
+
event: LangGraphEventTypes.OnCopilotKitInterrupt,
|
|
987
|
+
data: {
|
|
988
|
+
value: typeof evValue === "string" ? evValue : JSON.stringify(evValue),
|
|
989
|
+
messages: langchainMessagesToCopilotKit(interruptValue.__copilotkit_messages__),
|
|
990
|
+
},
|
|
991
|
+
}) + "\n",
|
|
992
|
+
);
|
|
993
|
+
} else {
|
|
994
|
+
emit(
|
|
995
|
+
JSON.stringify({
|
|
996
|
+
event: LangGraphEventTypes.OnInterrupt,
|
|
997
|
+
value: typeof interruptValue === "string" ? interruptValue : JSON.stringify(interruptValue),
|
|
998
|
+
}) + "\n",
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
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
|
+
}
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
} from "./utils";
|
|
30
30
|
|
|
31
31
|
import { randomId, randomUUID } from "@copilotkit/shared";
|
|
32
|
+
import { convertServiceAdapterError } from "../shared";
|
|
32
33
|
|
|
33
34
|
const DEFAULT_MODEL = "claude-3-5-sonnet-latest";
|
|
34
35
|
|
|
@@ -60,6 +61,36 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
private shouldGenerateFallbackResponse(messages: Anthropic.Messages.MessageParam[]): boolean {
|
|
65
|
+
if (messages.length === 0) return false;
|
|
66
|
+
|
|
67
|
+
const lastMessage = messages[messages.length - 1];
|
|
68
|
+
|
|
69
|
+
// Check if the last message is a tool result
|
|
70
|
+
const endsWithToolResult =
|
|
71
|
+
lastMessage.role === "user" &&
|
|
72
|
+
Array.isArray(lastMessage.content) &&
|
|
73
|
+
lastMessage.content.some((content: any) => content.type === "tool_result");
|
|
74
|
+
|
|
75
|
+
// Also check if we have a recent pattern of user message -> assistant tool use -> user tool result
|
|
76
|
+
// This indicates a completed action that might not need a response
|
|
77
|
+
if (messages.length >= 3 && endsWithToolResult) {
|
|
78
|
+
const lastThree = messages.slice(-3);
|
|
79
|
+
const hasRecentToolPattern =
|
|
80
|
+
lastThree[0]?.role === "user" && // Initial user message
|
|
81
|
+
lastThree[1]?.role === "assistant" && // Assistant tool use
|
|
82
|
+
Array.isArray(lastThree[1].content) &&
|
|
83
|
+
lastThree[1].content.some((content: any) => content.type === "tool_use") &&
|
|
84
|
+
lastThree[2]?.role === "user" && // Tool result
|
|
85
|
+
Array.isArray(lastThree[2].content) &&
|
|
86
|
+
lastThree[2].content.some((content: any) => content.type === "tool_result");
|
|
87
|
+
|
|
88
|
+
return hasRecentToolPattern;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return endsWithToolResult;
|
|
92
|
+
}
|
|
93
|
+
|
|
63
94
|
async process(
|
|
64
95
|
request: CopilotRuntimeChatCompletionRequest,
|
|
65
96
|
): Promise<CopilotRuntimeChatCompletionResponse> {
|
|
@@ -94,24 +125,30 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
|
|
|
94
125
|
}
|
|
95
126
|
|
|
96
127
|
// Step 2: Map each message to an Anthropic message, eliminating invalid tool_results
|
|
128
|
+
const processedToolResultIds = new Set<string>();
|
|
97
129
|
const anthropicMessages = messages
|
|
98
130
|
.map((message) => {
|
|
99
|
-
// For tool results, only include if they match a valid tool_use ID
|
|
131
|
+
// For tool results, only include if they match a valid tool_use ID AND haven't been processed
|
|
100
132
|
if (message.isResultMessage()) {
|
|
101
133
|
// Skip if there's no corresponding tool_use
|
|
102
134
|
if (!validToolUseIds.has(message.actionExecutionId)) {
|
|
103
135
|
return null; // Will be filtered out later
|
|
104
136
|
}
|
|
105
137
|
|
|
106
|
-
//
|
|
107
|
-
|
|
138
|
+
// Skip if we've already processed a result for this tool_use ID
|
|
139
|
+
if (processedToolResultIds.has(message.actionExecutionId)) {
|
|
140
|
+
return null; // Will be filtered out later
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Mark this tool result as processed
|
|
144
|
+
processedToolResultIds.add(message.actionExecutionId);
|
|
108
145
|
|
|
109
146
|
return {
|
|
110
147
|
role: "user",
|
|
111
148
|
content: [
|
|
112
149
|
{
|
|
113
150
|
type: "tool_result",
|
|
114
|
-
content: message.result,
|
|
151
|
+
content: message.result || "Action completed successfully",
|
|
115
152
|
tool_use_id: message.actionExecutionId,
|
|
116
153
|
},
|
|
117
154
|
],
|
|
@@ -139,6 +176,7 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
|
|
|
139
176
|
// Apply token limits
|
|
140
177
|
const limitedMessages = limitMessagesToTokenCount(anthropicMessages, tools, model);
|
|
141
178
|
|
|
179
|
+
// We'll check if we need a fallback response after seeing what Anthropic returns
|
|
142
180
|
// We skip grouping by role since we've already ensured uniqueness of tool_results
|
|
143
181
|
|
|
144
182
|
let toolChoice: any = forwardedParameters?.toolChoice;
|
|
@@ -171,12 +209,14 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
|
|
|
171
209
|
let currentMessageId = randomId();
|
|
172
210
|
let currentToolCallId = randomId();
|
|
173
211
|
let filterThinkingTextBuffer = new FilterThinkingTextBuffer();
|
|
212
|
+
let hasReceivedContent = false;
|
|
174
213
|
|
|
175
214
|
try {
|
|
176
215
|
for await (const chunk of stream as AsyncIterable<any>) {
|
|
177
216
|
if (chunk.type === "message_start") {
|
|
178
217
|
currentMessageId = chunk.message.id;
|
|
179
218
|
} else if (chunk.type === "content_block_start") {
|
|
219
|
+
hasReceivedContent = true;
|
|
180
220
|
if (chunk.content_block.type === "text") {
|
|
181
221
|
didOutputText = false;
|
|
182
222
|
filterThinkingTextBuffer.reset();
|
|
@@ -220,15 +260,34 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
|
|
|
220
260
|
}
|
|
221
261
|
}
|
|
222
262
|
} catch (error) {
|
|
223
|
-
|
|
224
|
-
|
|
263
|
+
throw convertServiceAdapterError(error, "Anthropic");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Generate fallback response only if Anthropic produced no content
|
|
267
|
+
if (!hasReceivedContent && this.shouldGenerateFallbackResponse(limitedMessages)) {
|
|
268
|
+
// Extract the tool result content for a more contextual response
|
|
269
|
+
let fallbackContent = "Task completed successfully.";
|
|
270
|
+
const lastMessage = limitedMessages[limitedMessages.length - 1];
|
|
271
|
+
if (lastMessage?.role === "user" && Array.isArray(lastMessage.content)) {
|
|
272
|
+
const toolResult = lastMessage.content.find((c: any) => c.type === "tool_result");
|
|
273
|
+
if (toolResult?.content && toolResult.content !== "Action completed successfully") {
|
|
274
|
+
fallbackContent = toolResult.content;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
currentMessageId = randomId();
|
|
279
|
+
eventStream$.sendTextMessageStart({ messageId: currentMessageId });
|
|
280
|
+
eventStream$.sendTextMessageContent({
|
|
281
|
+
messageId: currentMessageId,
|
|
282
|
+
content: fallbackContent,
|
|
283
|
+
});
|
|
284
|
+
eventStream$.sendTextMessageEnd({ messageId: currentMessageId });
|
|
225
285
|
}
|
|
226
286
|
|
|
227
287
|
eventStream$.complete();
|
|
228
288
|
});
|
|
229
289
|
} catch (error) {
|
|
230
|
-
|
|
231
|
-
throw error;
|
|
290
|
+
throw convertServiceAdapterError(error, "Anthropic");
|
|
232
291
|
}
|
|
233
292
|
|
|
234
293
|
return {
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ActionExecutionMessage,
|
|
3
|
-
Message,
|
|
4
|
-
ResultMessage,
|
|
5
|
-
TextMessage,
|
|
6
|
-
} from "../../graphql/types/converted";
|
|
7
|
-
import { ActionInput } from "../../graphql/inputs/action.input";
|
|
8
1
|
import { Anthropic } from "@anthropic-ai/sdk";
|
|
2
|
+
import { ActionInput } from "../../graphql/inputs/action.input";
|
|
3
|
+
import { Message } from "../../graphql/types/converted";
|
|
9
4
|
|
|
10
5
|
export function limitMessagesToTokenCount(
|
|
11
6
|
messages: any[],
|
|
@@ -148,7 +143,7 @@ export function convertMessageToAnthropicMessage(
|
|
|
148
143
|
content: [
|
|
149
144
|
{
|
|
150
145
|
type: "tool_result",
|
|
151
|
-
content: message.result,
|
|
146
|
+
content: message.result || "Action completed successfully",
|
|
152
147
|
tool_use_id: message.actionExecutionId,
|
|
153
148
|
},
|
|
154
149
|
],
|