@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
|
@@ -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
|
],
|
|
@@ -1,37 +1,32 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Action,
|
|
3
|
-
randomId,
|
|
4
3
|
CopilotKitError,
|
|
5
4
|
CopilotKitErrorCode,
|
|
6
5
|
CopilotKitLowLevelError,
|
|
6
|
+
ensureStructuredError,
|
|
7
|
+
randomId,
|
|
8
|
+
Severity,
|
|
7
9
|
} from "@copilotkit/shared";
|
|
10
|
+
import { plainToInstance } from "class-transformer";
|
|
8
11
|
import {
|
|
9
|
-
|
|
12
|
+
catchError,
|
|
10
13
|
concat,
|
|
11
|
-
scan,
|
|
12
14
|
concatMap,
|
|
13
|
-
|
|
14
|
-
Subject,
|
|
15
|
+
EMPTY,
|
|
15
16
|
firstValueFrom,
|
|
16
17
|
from,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
of,
|
|
19
|
+
ReplaySubject,
|
|
20
|
+
scan,
|
|
21
|
+
Subject,
|
|
20
22
|
} from "rxjs";
|
|
21
|
-
import {
|
|
23
|
+
import { ActionInput } from "../graphql/inputs/action.input";
|
|
24
|
+
import { ActionExecutionMessage, ResultMessage, TextMessage } from "../graphql/types/converted";
|
|
22
25
|
import { GuardrailsResult } from "../graphql/types/guardrails-result.type";
|
|
23
|
-
import telemetry from "../lib/telemetry-client";
|
|
24
26
|
import { isRemoteAgentAction } from "../lib/runtime/remote-actions";
|
|
25
|
-
import {
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
ResultMessage,
|
|
29
|
-
TextMessage,
|
|
30
|
-
Message,
|
|
31
|
-
} from "../graphql/types/converted";
|
|
32
|
-
import { plainToInstance } from "class-transformer";
|
|
33
|
-
import { MessageRole } from "../graphql/types/enums";
|
|
34
|
-
import { parseJson, tryMap } from "@copilotkit/shared";
|
|
27
|
+
import { generateHelpfulErrorMessage } from "../lib/streaming";
|
|
28
|
+
import telemetry from "../lib/telemetry-client";
|
|
29
|
+
import { streamLangChainResponse } from "./langchain/utils";
|
|
35
30
|
|
|
36
31
|
export enum RuntimeEventTypes {
|
|
37
32
|
TextMessageStart = "TextMessageStart",
|
|
@@ -245,6 +240,16 @@ export class RuntimeEventSubject extends ReplaySubject<RuntimeEvent> {
|
|
|
245
240
|
export class RuntimeEventSource {
|
|
246
241
|
private eventStream$ = new RuntimeEventSubject();
|
|
247
242
|
private callback!: EventSourceCallback;
|
|
243
|
+
private errorHandler?: (error: any, context: any) => Promise<void>;
|
|
244
|
+
private errorContext?: any;
|
|
245
|
+
|
|
246
|
+
constructor(params?: {
|
|
247
|
+
errorHandler?: (error: any, context: any) => Promise<void>;
|
|
248
|
+
errorContext?: any;
|
|
249
|
+
}) {
|
|
250
|
+
this.errorHandler = params?.errorHandler;
|
|
251
|
+
this.errorContext = params?.errorContext;
|
|
252
|
+
}
|
|
248
253
|
|
|
249
254
|
async stream(callback: EventSourceCallback): Promise<void> {
|
|
250
255
|
this.callback = callback;
|
|
@@ -272,11 +277,19 @@ export class RuntimeEventSource {
|
|
|
272
277
|
actionInputsWithoutAgents: ActionInput[];
|
|
273
278
|
threadId: string;
|
|
274
279
|
}) {
|
|
275
|
-
this.callback(this.eventStream$).catch((error) => {
|
|
276
|
-
|
|
280
|
+
this.callback(this.eventStream$).catch(async (error) => {
|
|
281
|
+
// Convert streaming errors to structured errors, but preserve already structured ones
|
|
282
|
+
const structuredError = ensureStructuredError(error, convertStreamingErrorToStructured);
|
|
283
|
+
|
|
284
|
+
// Call the runtime error handler if provided
|
|
285
|
+
if (this.errorHandler && this.errorContext) {
|
|
286
|
+
try {
|
|
287
|
+
await this.errorHandler(structuredError, this.errorContext);
|
|
288
|
+
} catch (errorHandlerError) {
|
|
289
|
+
console.error("Error in streaming error handler:", errorHandlerError);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
277
292
|
|
|
278
|
-
// Convert streaming errors to structured errors
|
|
279
|
-
const structuredError = convertStreamingErrorToStructured(error);
|
|
280
293
|
this.eventStream$.error(structuredError);
|
|
281
294
|
this.eventStream$.complete();
|
|
282
295
|
});
|
|
@@ -330,17 +343,35 @@ export class RuntimeEventSource {
|
|
|
330
343
|
eventWithState.actionExecutionId,
|
|
331
344
|
actionInputsWithoutAgents,
|
|
332
345
|
threadId,
|
|
333
|
-
).catch((error) => {
|
|
334
|
-
console.error(error);
|
|
335
|
-
});
|
|
346
|
+
).catch((error) => {});
|
|
336
347
|
|
|
337
348
|
telemetry.capture("oss.runtime.server_action_executed", {});
|
|
338
349
|
return concat(of(eventWithState.event!), toolCallEventStream$).pipe(
|
|
339
350
|
catchError((error) => {
|
|
340
|
-
|
|
351
|
+
// Convert streaming errors to structured errors and send as action result, but preserve already structured ones
|
|
352
|
+
const structuredError = ensureStructuredError(
|
|
353
|
+
error,
|
|
354
|
+
convertStreamingErrorToStructured,
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
// Call the runtime error handler if provided
|
|
358
|
+
if (this.errorHandler && this.errorContext) {
|
|
359
|
+
// Use from() to handle async error handler
|
|
360
|
+
from(
|
|
361
|
+
this.errorHandler(structuredError, {
|
|
362
|
+
...this.errorContext,
|
|
363
|
+
action: {
|
|
364
|
+
name: eventWithState.action!.name,
|
|
365
|
+
executionId: eventWithState.actionExecutionId,
|
|
366
|
+
},
|
|
367
|
+
}),
|
|
368
|
+
).subscribe({
|
|
369
|
+
error: (errorHandlerError) => {
|
|
370
|
+
console.error("Error in action execution error handler:", errorHandlerError);
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
}
|
|
341
374
|
|
|
342
|
-
// Convert streaming errors to structured errors and send as action result
|
|
343
|
-
const structuredError = convertStreamingErrorToStructured(error);
|
|
344
375
|
toolCallEventStream$.sendActionExecutionResult({
|
|
345
376
|
actionExecutionId: eventWithState.actionExecutionId!,
|
|
346
377
|
actionName: eventWithState.action!.name,
|
|
@@ -436,10 +467,8 @@ async function executeAction(
|
|
|
436
467
|
from(stream).subscribe({
|
|
437
468
|
next: (event) => eventStream$.next(event),
|
|
438
469
|
error: (err) => {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
// Convert streaming errors to structured errors
|
|
442
|
-
const structuredError = convertStreamingErrorToStructured(err);
|
|
470
|
+
// Preserve already structured CopilotKit errors, only convert unstructured errors
|
|
471
|
+
const structuredError = ensureStructuredError(err, convertStreamingErrorToStructured);
|
|
443
472
|
eventStream$.sendActionExecutionResult({
|
|
444
473
|
actionExecutionId,
|
|
445
474
|
actionName: action.name,
|
|
@@ -480,65 +509,31 @@ async function executeAction(
|
|
|
480
509
|
}
|
|
481
510
|
|
|
482
511
|
function convertStreamingErrorToStructured(error: any): CopilotKitError {
|
|
483
|
-
//
|
|
484
|
-
|
|
485
|
-
error?.message?.includes("terminated") ||
|
|
486
|
-
error?.cause?.code === "UND_ERR_SOCKET" ||
|
|
487
|
-
error?.message?.includes("other side closed") ||
|
|
488
|
-
error?.code === "UND_ERR_SOCKET"
|
|
489
|
-
) {
|
|
490
|
-
return new CopilotKitError({
|
|
491
|
-
message:
|
|
492
|
-
"Connection to agent was unexpectedly terminated. This may be due to the agent service being restarted or network issues. Please try again.",
|
|
493
|
-
code: CopilotKitErrorCode.NETWORK_ERROR,
|
|
494
|
-
});
|
|
495
|
-
}
|
|
512
|
+
// Determine a more helpful error message based on context
|
|
513
|
+
let helpfulMessage = generateHelpfulErrorMessage(error, "event streaming connection");
|
|
496
514
|
|
|
497
|
-
//
|
|
515
|
+
// For network-related errors, use CopilotKitLowLevelError to preserve the original error
|
|
498
516
|
if (
|
|
499
517
|
error?.message?.includes("fetch failed") ||
|
|
500
518
|
error?.message?.includes("ECONNREFUSED") ||
|
|
501
519
|
error?.message?.includes("ENOTFOUND") ||
|
|
502
|
-
error?.message?.includes("ETIMEDOUT")
|
|
520
|
+
error?.message?.includes("ETIMEDOUT") ||
|
|
521
|
+
error?.message?.includes("terminated") ||
|
|
522
|
+
error?.cause?.code === "UND_ERR_SOCKET" ||
|
|
523
|
+
error?.message?.includes("other side closed") ||
|
|
524
|
+
error?.code === "UND_ERR_SOCKET"
|
|
503
525
|
) {
|
|
504
526
|
return new CopilotKitLowLevelError({
|
|
505
527
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
506
528
|
url: "event streaming connection",
|
|
507
|
-
message:
|
|
508
|
-
"Network error occurred during event streaming. Please check your connection and try again.",
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// Handle abort/cancellation errors (these are usually normal)
|
|
513
|
-
if (
|
|
514
|
-
error?.message?.includes("aborted") ||
|
|
515
|
-
error?.message?.includes("canceled") ||
|
|
516
|
-
error?.message?.includes("signal is aborted")
|
|
517
|
-
) {
|
|
518
|
-
return new CopilotKitError({
|
|
519
|
-
message: "Request was cancelled",
|
|
520
|
-
code: CopilotKitErrorCode.UNKNOWN,
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// Handle API key errors (authentication/authorization issues)
|
|
525
|
-
const errorMessage = error?.message || String(error);
|
|
526
|
-
if (
|
|
527
|
-
errorMessage.includes("401") ||
|
|
528
|
-
errorMessage.toLowerCase().includes("api key") ||
|
|
529
|
-
errorMessage.toLowerCase().includes("unauthorized") ||
|
|
530
|
-
errorMessage.toLowerCase().includes("authentication") ||
|
|
531
|
-
errorMessage.toLowerCase().includes("incorrect api key")
|
|
532
|
-
) {
|
|
533
|
-
return new CopilotKitError({
|
|
534
|
-
message: `Event streaming error: ${errorMessage}`,
|
|
535
|
-
code: CopilotKitErrorCode.MISSING_PUBLIC_API_KEY_ERROR,
|
|
529
|
+
message: helpfulMessage,
|
|
536
530
|
});
|
|
537
531
|
}
|
|
538
532
|
|
|
539
|
-
//
|
|
533
|
+
// For all other errors, preserve the raw error in a basic CopilotKitError
|
|
540
534
|
return new CopilotKitError({
|
|
541
|
-
message:
|
|
535
|
+
message: helpfulMessage,
|
|
542
536
|
code: CopilotKitErrorCode.UNKNOWN,
|
|
537
|
+
severity: Severity.CRITICAL,
|
|
543
538
|
});
|
|
544
539
|
}
|
|
@@ -23,6 +23,10 @@ interface GoogleGenerativeAIAdapterOptions {
|
|
|
23
23
|
* A custom Google Generative AI model to use.
|
|
24
24
|
*/
|
|
25
25
|
model?: string;
|
|
26
|
+
/**
|
|
27
|
+
* The API key to use.
|
|
28
|
+
*/
|
|
29
|
+
apiKey?: string;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
export class GoogleGenerativeAIAdapter extends LangChainAdapter {
|
|
@@ -46,6 +50,7 @@ export class GoogleGenerativeAIAdapter extends LangChainAdapter {
|
|
|
46
50
|
});
|
|
47
51
|
|
|
48
52
|
const model = new ChatGoogle({
|
|
53
|
+
apiKey: options?.apiKey ?? process.env.GOOGLE_API_KEY,
|
|
49
54
|
modelName: options?.model ?? "gemini-1.5-pro",
|
|
50
55
|
apiVersion: "v1beta",
|
|
51
56
|
}).bindTools(tools);
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
limitMessagesToTokenCount,
|
|
28
28
|
} from "../openai/utils";
|
|
29
29
|
import { randomUUID } from "@copilotkit/shared";
|
|
30
|
+
import { convertServiceAdapterError } from "../shared";
|
|
30
31
|
|
|
31
32
|
const DEFAULT_MODEL = "llama-3.3-70b-versatile";
|
|
32
33
|
|
|
@@ -94,76 +95,85 @@ export class GroqAdapter implements CopilotServiceAdapter {
|
|
|
94
95
|
function: { name: forwardedParameters.toolChoiceFunctionName },
|
|
95
96
|
};
|
|
96
97
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
stream
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
98
|
+
let stream;
|
|
99
|
+
try {
|
|
100
|
+
stream = await this.groq.chat.completions.create({
|
|
101
|
+
model: model,
|
|
102
|
+
stream: true,
|
|
103
|
+
messages: openaiMessages as unknown as ChatCompletionMessageParam[],
|
|
104
|
+
...(tools.length > 0 && { tools }),
|
|
105
|
+
...(forwardedParameters?.maxTokens && {
|
|
106
|
+
max_tokens: forwardedParameters.maxTokens,
|
|
107
|
+
}),
|
|
108
|
+
...(forwardedParameters?.stop && { stop: forwardedParameters.stop }),
|
|
109
|
+
...(toolChoice && { tool_choice: toolChoice }),
|
|
110
|
+
...(this.disableParallelToolCalls && { parallel_tool_calls: false }),
|
|
111
|
+
...(forwardedParameters?.temperature && { temperature: forwardedParameters.temperature }),
|
|
112
|
+
});
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw convertServiceAdapterError(error, "Groq");
|
|
115
|
+
}
|
|
110
116
|
|
|
111
117
|
eventSource.stream(async (eventStream$) => {
|
|
112
118
|
let mode: "function" | "message" | null = null;
|
|
113
119
|
let currentMessageId: string;
|
|
114
120
|
let currentToolCallId: string;
|
|
115
121
|
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
122
|
+
try {
|
|
123
|
+
for await (const chunk of stream) {
|
|
124
|
+
const toolCall = chunk.choices[0].delta.tool_calls?.[0];
|
|
125
|
+
const content = chunk.choices[0].delta.content;
|
|
126
|
+
|
|
127
|
+
// When switching from message to function or vice versa,
|
|
128
|
+
// send the respective end event.
|
|
129
|
+
// If toolCall?.id is defined, it means a new tool call starts.
|
|
130
|
+
if (mode === "message" && toolCall?.id) {
|
|
131
|
+
mode = null;
|
|
132
|
+
eventStream$.sendTextMessageEnd({ messageId: currentMessageId });
|
|
133
|
+
} else if (mode === "function" && (toolCall === undefined || toolCall?.id)) {
|
|
134
|
+
mode = null;
|
|
135
|
+
eventStream$.sendActionExecutionEnd({ actionExecutionId: currentToolCallId });
|
|
136
|
+
}
|
|
119
137
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
138
|
+
// If we send a new message type, send the appropriate start event.
|
|
139
|
+
if (mode === null) {
|
|
140
|
+
if (toolCall?.id) {
|
|
141
|
+
mode = "function";
|
|
142
|
+
currentToolCallId = toolCall!.id;
|
|
143
|
+
eventStream$.sendActionExecutionStart({
|
|
144
|
+
actionExecutionId: currentToolCallId,
|
|
145
|
+
actionName: toolCall!.function!.name,
|
|
146
|
+
parentMessageId: chunk.id,
|
|
147
|
+
});
|
|
148
|
+
} else if (content) {
|
|
149
|
+
mode = "message";
|
|
150
|
+
currentMessageId = chunk.id;
|
|
151
|
+
eventStream$.sendTextMessageStart({ messageId: currentMessageId });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
130
154
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
155
|
+
// send the content events
|
|
156
|
+
if (mode === "message" && content) {
|
|
157
|
+
eventStream$.sendTextMessageContent({
|
|
158
|
+
messageId: currentMessageId,
|
|
159
|
+
content,
|
|
160
|
+
});
|
|
161
|
+
} else if (mode === "function" && toolCall?.function?.arguments) {
|
|
162
|
+
eventStream$.sendActionExecutionArgs({
|
|
137
163
|
actionExecutionId: currentToolCallId,
|
|
138
|
-
|
|
139
|
-
parentMessageId: chunk.id,
|
|
164
|
+
args: toolCall.function.arguments,
|
|
140
165
|
});
|
|
141
|
-
} else if (content) {
|
|
142
|
-
mode = "message";
|
|
143
|
-
currentMessageId = chunk.id;
|
|
144
|
-
eventStream$.sendTextMessageStart({ messageId: currentMessageId });
|
|
145
166
|
}
|
|
146
167
|
}
|
|
147
168
|
|
|
148
|
-
// send the
|
|
149
|
-
if (mode === "message"
|
|
150
|
-
eventStream$.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
});
|
|
154
|
-
} else if (mode === "function" && toolCall?.function?.arguments) {
|
|
155
|
-
eventStream$.sendActionExecutionArgs({
|
|
156
|
-
actionExecutionId: currentToolCallId,
|
|
157
|
-
args: toolCall.function.arguments,
|
|
158
|
-
});
|
|
169
|
+
// send the end events
|
|
170
|
+
if (mode === "message") {
|
|
171
|
+
eventStream$.sendTextMessageEnd({ messageId: currentMessageId });
|
|
172
|
+
} else if (mode === "function") {
|
|
173
|
+
eventStream$.sendActionExecutionEnd({ actionExecutionId: currentToolCallId });
|
|
159
174
|
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// send the end events
|
|
163
|
-
if (mode === "message") {
|
|
164
|
-
eventStream$.sendTextMessageEnd({ messageId: currentMessageId });
|
|
165
|
-
} else if (mode === "function") {
|
|
166
|
-
eventStream$.sendActionExecutionEnd({ actionExecutionId: currentToolCallId });
|
|
175
|
+
} catch (error) {
|
|
176
|
+
throw convertServiceAdapterError(error, "Groq");
|
|
167
177
|
}
|
|
168
178
|
|
|
169
179
|
eventStream$.complete();
|
|
@@ -5,6 +5,7 @@ export type {
|
|
|
5
5
|
} from "./service-adapter";
|
|
6
6
|
export type { RemoteChainParameters } from "./langchain/langserve";
|
|
7
7
|
export { RemoteChain } from "./langchain/langserve";
|
|
8
|
+
export * from "./shared";
|
|
8
9
|
export * from "./openai/openai-adapter";
|
|
9
10
|
export * from "./langchain/langchain-adapter";
|
|
10
11
|
export * from "./google/google-genai-adapter";
|
|
@@ -60,6 +60,7 @@ import {
|
|
|
60
60
|
limitMessagesToTokenCount,
|
|
61
61
|
} from "./utils";
|
|
62
62
|
import { randomUUID } from "@copilotkit/shared";
|
|
63
|
+
import { convertServiceAdapterError } from "../shared";
|
|
63
64
|
|
|
64
65
|
const DEFAULT_MODEL = "gpt-4o";
|
|
65
66
|
|
|
@@ -244,15 +245,15 @@ export class OpenAIAdapter implements CopilotServiceAdapter {
|
|
|
244
245
|
eventStream$.sendActionExecutionEnd({ actionExecutionId: currentToolCallId });
|
|
245
246
|
}
|
|
246
247
|
} catch (error) {
|
|
247
|
-
console.error("[OpenAI] Error
|
|
248
|
-
throw error;
|
|
248
|
+
console.error("[OpenAI] Error during API call:", error);
|
|
249
|
+
throw convertServiceAdapterError(error, "OpenAI");
|
|
249
250
|
}
|
|
250
251
|
|
|
251
252
|
eventStream$.complete();
|
|
252
253
|
});
|
|
253
254
|
} catch (error) {
|
|
254
255
|
console.error("[OpenAI] Error during API call:", error);
|
|
255
|
-
throw error;
|
|
256
|
+
throw convertServiceAdapterError(error, "OpenAI");
|
|
256
257
|
}
|
|
257
258
|
|
|
258
259
|
return {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { CopilotKitLowLevelError, CopilotKitErrorCode } from "@copilotkit/shared";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Converts service adapter errors to structured CopilotKitError format using HTTP status codes.
|
|
5
|
+
* This provides consistent error classification across all service adapters.
|
|
6
|
+
*/
|
|
7
|
+
export function convertServiceAdapterError(
|
|
8
|
+
error: any,
|
|
9
|
+
adapterName: string,
|
|
10
|
+
): CopilotKitLowLevelError {
|
|
11
|
+
const errorName = error?.constructor?.name || error.name;
|
|
12
|
+
const errorMessage = error?.message || String(error);
|
|
13
|
+
const statusCode = error.status || error.statusCode || error.response?.status;
|
|
14
|
+
const responseData = error.error || error.response?.data || error.data;
|
|
15
|
+
|
|
16
|
+
// Create the base error with the constructor signature
|
|
17
|
+
const structuredError = new CopilotKitLowLevelError({
|
|
18
|
+
error: error instanceof Error ? error : new Error(errorMessage),
|
|
19
|
+
url: `${adapterName} service adapter`,
|
|
20
|
+
message: `${adapterName} API error: ${errorMessage}`,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Add additional properties after construction
|
|
24
|
+
if (statusCode) {
|
|
25
|
+
(structuredError as any).statusCode = statusCode;
|
|
26
|
+
}
|
|
27
|
+
if (responseData) {
|
|
28
|
+
(structuredError as any).responseData = responseData;
|
|
29
|
+
}
|
|
30
|
+
if (errorName) {
|
|
31
|
+
(structuredError as any).originalErrorType = errorName;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Classify error based on HTTP status codes (reliable and provider-agnostic)
|
|
35
|
+
let newCode: CopilotKitErrorCode;
|
|
36
|
+
|
|
37
|
+
if (statusCode === 401) {
|
|
38
|
+
// 401 = Authentication/API key issues
|
|
39
|
+
newCode = CopilotKitErrorCode.AUTHENTICATION_ERROR;
|
|
40
|
+
} else if (statusCode >= 400 && statusCode < 500) {
|
|
41
|
+
// 4xx = Client errors (bad request, invalid params, etc.) - these are configuration issues
|
|
42
|
+
newCode = CopilotKitErrorCode.CONFIGURATION_ERROR;
|
|
43
|
+
} else if (statusCode >= 500) {
|
|
44
|
+
// 5xx = Server errors - keep as NETWORK_ERROR since it's infrastructure related
|
|
45
|
+
newCode = CopilotKitErrorCode.NETWORK_ERROR;
|
|
46
|
+
} else if (statusCode) {
|
|
47
|
+
// Any other HTTP status with an error - likely configuration
|
|
48
|
+
newCode = CopilotKitErrorCode.CONFIGURATION_ERROR;
|
|
49
|
+
} else {
|
|
50
|
+
// No status code - likely a genuine network/connection error
|
|
51
|
+
newCode = CopilotKitErrorCode.NETWORK_ERROR;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Update both the instance property and the extensions
|
|
55
|
+
(structuredError as any).code = newCode;
|
|
56
|
+
if ((structuredError as any).extensions) {
|
|
57
|
+
(structuredError as any).extensions.code = newCode;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return structuredError;
|
|
61
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./error-utils";
|