@copilotkit/runtime 1.9.2-next.9 → 1.9.3-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +176 -0
  2. package/dist/{chunk-Z5GYTKMD.mjs → chunk-EK5RTZVJ.mjs} +225 -149
  3. package/dist/chunk-EK5RTZVJ.mjs.map +1 -0
  4. package/dist/{chunk-SMDVD4VG.mjs → chunk-KCYFFRJY.mjs} +2 -2
  5. package/dist/{chunk-4JBKY7XT.mjs → chunk-QLLV2QVK.mjs} +48 -28
  6. package/dist/chunk-QLLV2QVK.mjs.map +1 -0
  7. package/dist/{chunk-5YGKE5SN.mjs → chunk-R5D7D7YN.mjs} +2 -2
  8. package/dist/{chunk-UUXRYAB4.mjs → chunk-RCCT2GOF.mjs} +2 -2
  9. package/dist/{chunk-ALZ5H3VD.mjs → chunk-YGS5B7PN.mjs} +2 -2
  10. package/dist/{groq-adapter-172a2ca4.d.ts → groq-adapter-742818f2.d.ts} +5 -1
  11. package/dist/index.d.ts +3 -3
  12. package/dist/index.js +267 -171
  13. package/dist/index.js.map +1 -1
  14. package/dist/index.mjs +9 -9
  15. package/dist/{langserve-fc5cac89.d.ts → langserve-3e8d0e06.d.ts} +6 -0
  16. package/dist/lib/index.d.ts +155 -5
  17. package/dist/lib/index.js +221 -168
  18. package/dist/lib/index.js.map +1 -1
  19. package/dist/lib/index.mjs +9 -9
  20. package/dist/lib/integrations/index.d.ts +3 -3
  21. package/dist/lib/integrations/index.js +11 -11
  22. package/dist/lib/integrations/index.js.map +1 -1
  23. package/dist/lib/integrations/index.mjs +8 -8
  24. package/dist/lib/integrations/nest/index.d.ts +2 -2
  25. package/dist/lib/integrations/nest/index.js +11 -11
  26. package/dist/lib/integrations/nest/index.js.map +1 -1
  27. package/dist/lib/integrations/nest/index.mjs +4 -4
  28. package/dist/lib/integrations/node-express/index.d.ts +2 -2
  29. package/dist/lib/integrations/node-express/index.js +11 -11
  30. package/dist/lib/integrations/node-express/index.js.map +1 -1
  31. package/dist/lib/integrations/node-express/index.mjs +4 -4
  32. package/dist/lib/integrations/node-http/index.d.ts +2 -2
  33. package/dist/lib/integrations/node-http/index.js +11 -11
  34. package/dist/lib/integrations/node-http/index.js.map +1 -1
  35. package/dist/lib/integrations/node-http/index.mjs +3 -3
  36. package/dist/service-adapters/index.d.ts +5 -4
  37. package/dist/service-adapters/index.js +47 -27
  38. package/dist/service-adapters/index.js.map +1 -1
  39. package/dist/service-adapters/index.mjs +1 -1
  40. package/dist/{shared-bd953ebf.d.ts → shared-96b46379.d.ts} +16 -18
  41. package/package.json +11 -11
  42. package/src/graphql/resolvers/copilot.resolver.ts +1 -2
  43. package/src/lib/runtime/__tests__/{copilot-runtime-trace.test.ts → copilot-runtime-error.test.ts} +27 -27
  44. package/src/lib/runtime/__tests__/mcp-tools-utils.test.ts +464 -0
  45. package/src/lib/runtime/agui-action.ts +9 -3
  46. package/src/lib/runtime/copilot-runtime.ts +112 -124
  47. package/src/lib/runtime/mcp-tools-utils.ts +84 -18
  48. package/src/lib/runtime/remote-actions.ts +6 -0
  49. package/src/service-adapters/anthropic/anthropic-adapter.ts +64 -4
  50. package/src/service-adapters/anthropic/utils.ts +3 -8
  51. package/src/service-adapters/events.ts +40 -1
  52. package/src/service-adapters/google/google-genai-adapter.ts +5 -0
  53. package/src/service-adapters/openai/openai-adapter.ts +0 -14
  54. package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +172 -387
  55. package/dist/chunk-4JBKY7XT.mjs.map +0 -1
  56. package/dist/chunk-Z5GYTKMD.mjs.map +0 -1
  57. /package/dist/{chunk-SMDVD4VG.mjs.map → chunk-KCYFFRJY.mjs.map} +0 -0
  58. /package/dist/{chunk-5YGKE5SN.mjs.map → chunk-R5D7D7YN.mjs.map} +0 -0
  59. /package/dist/{chunk-UUXRYAB4.mjs.map → chunk-RCCT2GOF.mjs.map} +0 -0
  60. /package/dist/{chunk-ALZ5H3VD.mjs.map → chunk-YGS5B7PN.mjs.map} +0 -0
@@ -61,6 +61,36 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
61
61
  }
62
62
  }
63
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
+
64
94
  async process(
65
95
  request: CopilotRuntimeChatCompletionRequest,
66
96
  ): Promise<CopilotRuntimeChatCompletionResponse> {
@@ -95,24 +125,30 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
95
125
  }
96
126
 
97
127
  // Step 2: Map each message to an Anthropic message, eliminating invalid tool_results
128
+ const processedToolResultIds = new Set<string>();
98
129
  const anthropicMessages = messages
99
130
  .map((message) => {
100
- // 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
101
132
  if (message.isResultMessage()) {
102
133
  // Skip if there's no corresponding tool_use
103
134
  if (!validToolUseIds.has(message.actionExecutionId)) {
104
135
  return null; // Will be filtered out later
105
136
  }
106
137
 
107
- // Remove this ID from valid IDs so we don't process duplicates
108
- 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);
109
145
 
110
146
  return {
111
147
  role: "user",
112
148
  content: [
113
149
  {
114
150
  type: "tool_result",
115
- content: message.result,
151
+ content: message.result || "Action completed successfully",
116
152
  tool_use_id: message.actionExecutionId,
117
153
  },
118
154
  ],
@@ -140,6 +176,7 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
140
176
  // Apply token limits
141
177
  const limitedMessages = limitMessagesToTokenCount(anthropicMessages, tools, model);
142
178
 
179
+ // We'll check if we need a fallback response after seeing what Anthropic returns
143
180
  // We skip grouping by role since we've already ensured uniqueness of tool_results
144
181
 
145
182
  let toolChoice: any = forwardedParameters?.toolChoice;
@@ -172,12 +209,14 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
172
209
  let currentMessageId = randomId();
173
210
  let currentToolCallId = randomId();
174
211
  let filterThinkingTextBuffer = new FilterThinkingTextBuffer();
212
+ let hasReceivedContent = false;
175
213
 
176
214
  try {
177
215
  for await (const chunk of stream as AsyncIterable<any>) {
178
216
  if (chunk.type === "message_start") {
179
217
  currentMessageId = chunk.message.id;
180
218
  } else if (chunk.type === "content_block_start") {
219
+ hasReceivedContent = true;
181
220
  if (chunk.content_block.type === "text") {
182
221
  didOutputText = false;
183
222
  filterThinkingTextBuffer.reset();
@@ -224,6 +263,27 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
224
263
  throw convertServiceAdapterError(error, "Anthropic");
225
264
  }
226
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 });
285
+ }
286
+
227
287
  eventStream$.complete();
228
288
  });
229
289
  } catch (error) {
@@ -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
  ],
@@ -240,6 +240,16 @@ export class RuntimeEventSubject extends ReplaySubject<RuntimeEvent> {
240
240
  export class RuntimeEventSource {
241
241
  private eventStream$ = new RuntimeEventSubject();
242
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
+ }
243
253
 
244
254
  async stream(callback: EventSourceCallback): Promise<void> {
245
255
  this.callback = callback;
@@ -267,9 +277,19 @@ export class RuntimeEventSource {
267
277
  actionInputsWithoutAgents: ActionInput[];
268
278
  threadId: string;
269
279
  }) {
270
- this.callback(this.eventStream$).catch((error) => {
280
+ this.callback(this.eventStream$).catch(async (error) => {
271
281
  // Convert streaming errors to structured errors, but preserve already structured ones
272
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
+ }
292
+
273
293
  this.eventStream$.error(structuredError);
274
294
  this.eventStream$.complete();
275
295
  });
@@ -333,6 +353,25 @@ export class RuntimeEventSource {
333
353
  error,
334
354
  convertStreamingErrorToStructured,
335
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
+ }
374
+
336
375
  toolCallEventStream$.sendActionExecutionResult({
337
376
  actionExecutionId: eventWithState.actionExecutionId!,
338
377
  actionName: eventWithState.action!.name,
@@ -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);
@@ -129,8 +129,6 @@ export class OpenAIAdapter implements CopilotServiceAdapter {
129
129
  const tools = actions.map(convertActionInputToOpenAITool);
130
130
  const threadId = threadIdFromRequest ?? randomUUID();
131
131
 
132
- console.log("messages", messages);
133
-
134
132
  // ALLOWLIST APPROACH: Only include tool_result messages that correspond to valid tool_calls
135
133
  // Step 1: Extract valid tool_call IDs
136
134
  const validToolUseIds = new Set<string>();
@@ -171,18 +169,6 @@ export class OpenAIAdapter implements CopilotServiceAdapter {
171
169
  };
172
170
  }
173
171
 
174
- console.log("INPUT", {
175
- model: model,
176
- stream: true,
177
- messages: openaiMessages,
178
- ...(tools.length > 0 && { tools }),
179
- ...(forwardedParameters?.maxTokens && { max_tokens: forwardedParameters.maxTokens }),
180
- ...(forwardedParameters?.stop && { stop: forwardedParameters.stop }),
181
- ...(toolChoice && { tool_choice: toolChoice }),
182
- ...(this.disableParallelToolCalls && { parallel_tool_calls: false }),
183
- ...(forwardedParameters?.temperature && { temperature: forwardedParameters.temperature }),
184
- });
185
-
186
172
  try {
187
173
  const stream = this.openai.beta.chat.completions.stream({
188
174
  model: model,