@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.
Files changed (86) hide show
  1. package/CHANGELOG.md +177 -0
  2. package/dist/{chunk-4TLMVLU4.mjs → chunk-56ZNYBXV.mjs} +2 -2
  3. package/dist/chunk-AMUJQ6IR.mjs +50 -0
  4. package/dist/chunk-AMUJQ6IR.mjs.map +1 -0
  5. package/dist/{chunk-5SG4WWXH.mjs → chunk-GB4M7WUE.mjs} +2 -2
  6. package/dist/{chunk-JWPSIGSA.mjs → chunk-HJYWUUFY.mjs} +2 -2
  7. package/dist/{chunk-KYCDL2KX.mjs → chunk-M35WOOEP.mjs} +2 -2
  8. package/dist/{chunk-IIXJVVTV.mjs → chunk-QLLV2QVK.mjs} +132 -78
  9. package/dist/chunk-QLLV2QVK.mjs.map +1 -0
  10. package/dist/{chunk-WIXS6EG7.mjs → chunk-TE5QWP4H.mjs} +2401 -2055
  11. package/dist/chunk-TE5QWP4H.mjs.map +1 -0
  12. package/dist/{chunk-5BIEM2UU.mjs → chunk-XWBDEXDA.mjs} +4 -3
  13. package/dist/{chunk-5BIEM2UU.mjs.map → chunk-XWBDEXDA.mjs.map} +1 -1
  14. package/dist/{groq-adapter-25a2bd35.d.ts → groq-adapter-742818f2.d.ts} +5 -1
  15. package/dist/index.d.ts +4 -3
  16. package/dist/index.js +3747 -3303
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.mjs +12 -8
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/{langserve-4a5c9217.d.ts → langserve-3e8d0e06.d.ts} +13 -7
  21. package/dist/lib/index.d.ts +155 -5
  22. package/dist/lib/index.js +2808 -2407
  23. package/dist/lib/index.js.map +1 -1
  24. package/dist/lib/index.mjs +9 -8
  25. package/dist/lib/integrations/index.d.ts +3 -3
  26. package/dist/lib/integrations/index.js +151 -96
  27. package/dist/lib/integrations/index.js.map +1 -1
  28. package/dist/lib/integrations/index.mjs +7 -6
  29. package/dist/lib/integrations/nest/index.d.ts +2 -2
  30. package/dist/lib/integrations/nest/index.js +151 -96
  31. package/dist/lib/integrations/nest/index.js.map +1 -1
  32. package/dist/lib/integrations/nest/index.mjs +5 -4
  33. package/dist/lib/integrations/node-express/index.d.ts +2 -2
  34. package/dist/lib/integrations/node-express/index.js +151 -96
  35. package/dist/lib/integrations/node-express/index.js.map +1 -1
  36. package/dist/lib/integrations/node-express/index.mjs +5 -4
  37. package/dist/lib/integrations/node-http/index.d.ts +2 -2
  38. package/dist/lib/integrations/node-http/index.js +151 -96
  39. package/dist/lib/integrations/node-http/index.js.map +1 -1
  40. package/dist/lib/integrations/node-http/index.mjs +4 -3
  41. package/dist/service-adapters/index.d.ts +6 -4
  42. package/dist/service-adapters/index.js +202 -107
  43. package/dist/service-adapters/index.js.map +1 -1
  44. package/dist/service-adapters/index.mjs +6 -2
  45. package/dist/service-adapters/shared/index.d.ts +9 -0
  46. package/dist/service-adapters/shared/index.js +72 -0
  47. package/dist/service-adapters/shared/index.js.map +1 -0
  48. package/dist/service-adapters/shared/index.mjs +8 -0
  49. package/dist/service-adapters/shared/index.mjs.map +1 -0
  50. package/dist/{shared-941d59dc.d.ts → shared-96b46379.d.ts} +23 -21
  51. package/dist/utils/index.d.ts +17 -1
  52. package/dist/utils/index.js +3 -2
  53. package/dist/utils/index.js.map +1 -1
  54. package/dist/utils/index.mjs +1 -1
  55. package/package.json +11 -11
  56. package/src/agents/langgraph/event-source.ts +36 -38
  57. package/src/agents/langgraph/events.ts +19 -1
  58. package/src/graphql/resolvers/copilot.resolver.ts +85 -42
  59. package/src/lib/error-messages.ts +200 -0
  60. package/src/lib/integrations/shared.ts +43 -0
  61. package/src/lib/runtime/__tests__/{copilot-runtime-trace.test.ts → copilot-runtime-error.test.ts} +27 -27
  62. package/src/lib/runtime/__tests__/mcp-tools-utils.test.ts +464 -0
  63. package/src/lib/runtime/agui-action.ts +9 -3
  64. package/src/lib/runtime/copilot-runtime.ts +156 -160
  65. package/src/lib/runtime/mcp-tools-utils.ts +84 -18
  66. package/src/lib/runtime/remote-action-constructors.ts +28 -3
  67. package/src/lib/runtime/remote-actions.ts +6 -0
  68. package/src/lib/runtime/remote-lg-action.ts +85 -3
  69. package/src/lib/streaming.ts +125 -36
  70. package/src/service-adapters/anthropic/anthropic-adapter.ts +67 -8
  71. package/src/service-adapters/anthropic/utils.ts +3 -8
  72. package/src/service-adapters/events.ts +75 -80
  73. package/src/service-adapters/google/google-genai-adapter.ts +5 -0
  74. package/src/service-adapters/groq/groq-adapter.ts +66 -56
  75. package/src/service-adapters/index.ts +1 -0
  76. package/src/service-adapters/openai/openai-adapter.ts +4 -3
  77. package/src/service-adapters/shared/error-utils.ts +61 -0
  78. package/src/service-adapters/shared/index.ts +1 -0
  79. package/src/utils/failed-response-status-reasons.ts +23 -1
  80. package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +172 -387
  81. package/dist/chunk-IIXJVVTV.mjs.map +0 -1
  82. package/dist/chunk-WIXS6EG7.mjs.map +0 -1
  83. /package/dist/{chunk-4TLMVLU4.mjs.map → chunk-56ZNYBXV.mjs.map} +0 -0
  84. /package/dist/{chunk-5SG4WWXH.mjs.map → chunk-GB4M7WUE.mjs.map} +0 -0
  85. /package/dist/{chunk-JWPSIGSA.mjs.map → chunk-HJYWUUFY.mjs.map} +0 -0
  86. /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
- // Remove this ID from valid IDs so we don't process duplicates
107
- validToolUseIds.delete(message.actionExecutionId);
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
- console.error("[Anthropic] Error processing stream:", error);
224
- throw error;
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
- console.error("[Anthropic] Error during API call:", error);
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
- of,
12
+ catchError,
10
13
  concat,
11
- scan,
12
14
  concatMap,
13
- ReplaySubject,
14
- Subject,
15
+ EMPTY,
15
16
  firstValueFrom,
16
17
  from,
17
- catchError,
18
- EMPTY,
19
- BehaviorSubject,
18
+ of,
19
+ ReplaySubject,
20
+ scan,
21
+ Subject,
20
22
  } from "rxjs";
21
- import { streamLangChainResponse } from "./langchain/utils";
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 { ActionInput } from "../graphql/inputs/action.input";
26
- import {
27
- ActionExecutionMessage,
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
- console.error("Error in event source callback", error);
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
- console.error("Error in tool call stream", error);
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
- console.error("Error in stream", err);
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
- // Handle network termination errors
484
- if (
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
- // Handle other network-related errors
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
- // Default: convert unknown streaming errors
533
+ // For all other errors, preserve the raw error in a basic CopilotKitError
540
534
  return new CopilotKitError({
541
- message: `Event streaming error: ${errorMessage}`,
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
- const stream = await this.groq.chat.completions.create({
98
- model: model,
99
- stream: true,
100
- messages: openaiMessages as unknown as ChatCompletionMessageParam[],
101
- ...(tools.length > 0 && { tools }),
102
- ...(forwardedParameters?.maxTokens && {
103
- max_tokens: forwardedParameters.maxTokens,
104
- }),
105
- ...(forwardedParameters?.stop && { stop: forwardedParameters.stop }),
106
- ...(toolChoice && { tool_choice: toolChoice }),
107
- ...(this.disableParallelToolCalls && { parallel_tool_calls: false }),
108
- ...(forwardedParameters?.temperature && { temperature: forwardedParameters.temperature }),
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
- for await (const chunk of stream) {
117
- const toolCall = chunk.choices[0].delta.tool_calls?.[0];
118
- const content = chunk.choices[0].delta.content;
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
- // When switching from message to function or vice versa,
121
- // send the respective end event.
122
- // If toolCall?.id is defined, it means a new tool call starts.
123
- if (mode === "message" && toolCall?.id) {
124
- mode = null;
125
- eventStream$.sendTextMessageEnd({ messageId: currentMessageId });
126
- } else if (mode === "function" && (toolCall === undefined || toolCall?.id)) {
127
- mode = null;
128
- eventStream$.sendActionExecutionEnd({ actionExecutionId: currentToolCallId });
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
- // If we send a new message type, send the appropriate start event.
132
- if (mode === null) {
133
- if (toolCall?.id) {
134
- mode = "function";
135
- currentToolCallId = toolCall!.id;
136
- eventStream$.sendActionExecutionStart({
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
- actionName: toolCall!.function!.name,
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 content events
149
- if (mode === "message" && content) {
150
- eventStream$.sendTextMessageContent({
151
- messageId: currentMessageId,
152
- content,
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 processing stream:", 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";