@copilotkit/runtime 1.9.2-next.1 → 1.9.2-next.11

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 (84) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/dist/chunk-AMUJQ6IR.mjs +50 -0
  3. package/dist/chunk-AMUJQ6IR.mjs.map +1 -0
  4. package/dist/{chunk-C3SWOFLO.mjs → chunk-B2BL6HPT.mjs} +2 -2
  5. package/dist/{chunk-RIPQZJB5.mjs → chunk-CBVVBPVJ.mjs} +2 -2
  6. package/dist/{chunk-IIXJVVTV.mjs → chunk-GS7DO47Q.mjs} +155 -78
  7. package/dist/chunk-GS7DO47Q.mjs.map +1 -0
  8. package/dist/{chunk-XGBY45FP.mjs → chunk-NI7RVCMB.mjs} +2343 -1857
  9. package/dist/chunk-NI7RVCMB.mjs.map +1 -0
  10. package/dist/{chunk-YV3YXRMR.mjs → chunk-VVXCPFVN.mjs} +19 -2
  11. package/dist/chunk-VVXCPFVN.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/{chunk-KPFOAXRX.mjs → chunk-ZIEDTGZF.mjs} +2 -2
  15. package/dist/{groq-adapter-25a2bd35.d.ts → groq-adapter-172a2ca4.d.ts} +1 -1
  16. package/dist/index.d.ts +4 -3
  17. package/dist/index.js +3487 -2863
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.mjs +12 -8
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/lib/index.d.ts +7 -133
  22. package/dist/lib/index.js +3329 -2748
  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 +162 -98
  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 +162 -98
  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 +162 -98
  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 +162 -98
  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 +225 -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-e272b15a.d.ts → shared-4164c674.d.ts} +45 -5
  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 +4 -4
  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 +108 -45
  59. package/src/graphql/resolvers/state.resolver.ts +3 -3
  60. package/src/lib/error-messages.ts +200 -0
  61. package/src/lib/integrations/shared.ts +43 -0
  62. package/src/lib/runtime/__tests__/copilot-runtime-trace.test.ts +169 -0
  63. package/src/lib/runtime/copilot-runtime.ts +383 -83
  64. package/src/lib/runtime/langgraph/langgraph-agent.ts +12 -0
  65. package/src/lib/runtime/remote-action-constructors.ts +28 -3
  66. package/src/lib/runtime/remote-lg-action.ts +130 -40
  67. package/src/lib/streaming.ts +125 -36
  68. package/src/service-adapters/anthropic/anthropic-adapter.ts +67 -8
  69. package/src/service-adapters/anthropic/utils.ts +3 -8
  70. package/src/service-adapters/events.ts +37 -81
  71. package/src/service-adapters/groq/groq-adapter.ts +66 -56
  72. package/src/service-adapters/index.ts +1 -0
  73. package/src/service-adapters/openai/openai-adapter.ts +18 -3
  74. package/src/service-adapters/shared/error-utils.ts +61 -0
  75. package/src/service-adapters/shared/index.ts +1 -0
  76. package/src/utils/failed-response-status-reasons.ts +23 -1
  77. package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +172 -387
  78. package/dist/chunk-IIXJVVTV.mjs.map +0 -1
  79. package/dist/chunk-XGBY45FP.mjs.map +0 -1
  80. package/dist/chunk-YV3YXRMR.mjs.map +0 -1
  81. package/dist/{chunk-C3SWOFLO.mjs.map → chunk-B2BL6HPT.mjs.map} +0 -0
  82. package/dist/{chunk-RIPQZJB5.mjs.map → chunk-CBVVBPVJ.mjs.map} +0 -0
  83. package/dist/{chunk-KPFOAXRX.mjs.map → chunk-ZIEDTGZF.mjs.map} +0 -0
  84. package/dist/{langserve-4a5c9217.d.ts → langserve-fc5cac89.d.ts} +7 -7
@@ -54,7 +54,11 @@ import telemetry from "../../lib/telemetry-client";
54
54
  import { randomId } from "@copilotkit/shared";
55
55
  import { AgentsResponse } from "../types/agents-response.type";
56
56
  import { LangGraphEventTypes } from "../../agents/langgraph/events";
57
- import { CopilotKitError } from "@copilotkit/shared";
57
+ import {
58
+ CopilotKitError,
59
+ CopilotKitLowLevelError,
60
+ isStructuredCopilotKitError,
61
+ } from "@copilotkit/shared";
58
62
 
59
63
  const invokeGuardrails = async ({
60
64
  baseUrl,
@@ -174,15 +178,34 @@ export class CopilotResolver {
174
178
  let copilotCloudPublicApiKey: string | null = null;
175
179
  let copilotCloudBaseUrl: string;
176
180
 
181
+ // Extract publicApiKey from headers for both cloud and non-cloud requests
182
+ // This enables onTrace functionality regardless of cloud configuration
183
+ const publicApiKeyFromHeaders = ctx.request.headers.get("x-copilotcloud-public-api-key");
184
+ if (publicApiKeyFromHeaders) {
185
+ copilotCloudPublicApiKey = publicApiKeyFromHeaders;
186
+ }
187
+
177
188
  if (data.cloud) {
178
189
  logger = logger.child({ cloud: true });
179
190
  logger.debug("Cloud configuration provided, checking for public API key in headers");
180
- const key = ctx.request.headers.get("x-copilotcloud-public-api-key");
181
- if (key) {
182
- logger.debug("Public API key found in headers");
183
- copilotCloudPublicApiKey = key;
184
- } else {
191
+
192
+ if (!copilotCloudPublicApiKey) {
185
193
  logger.error("Public API key not found in headers");
194
+
195
+ // Trace the validation error for debugging visibility
196
+ await copilotRuntime.traceGraphQLError(
197
+ {
198
+ message: "X-CopilotCloud-Public-API-Key header is required",
199
+ code: "MISSING_PUBLIC_API_KEY",
200
+ type: "GraphQLError",
201
+ },
202
+ {
203
+ operation: "generateCopilotResponse",
204
+ cloudConfigPresent: Boolean(data.cloud),
205
+ guardrailsEnabled: Boolean(data.cloud?.guardrails),
206
+ },
207
+ );
208
+
186
209
  throw new GraphQLError("X-CopilotCloud-Public-API-Key header is required");
187
210
  }
188
211
 
@@ -216,6 +239,40 @@ export class CopilotResolver {
216
239
  }
217
240
 
218
241
  logger.debug("Processing");
242
+ let runtimeResponse;
243
+ try {
244
+ runtimeResponse = await copilotRuntime.processRuntimeRequest({
245
+ serviceAdapter,
246
+ messages: data.messages,
247
+ actions: data.frontend.actions.filter(
248
+ (action) => action.available !== ActionInputAvailability.disabled,
249
+ ),
250
+ threadId: data.threadId,
251
+ runId: data.runId,
252
+ publicApiKey: copilotCloudPublicApiKey,
253
+ outputMessagesPromise,
254
+ graphqlContext: ctx,
255
+ forwardedParameters: data.forwardedParameters,
256
+ agentSession: data.agentSession,
257
+ agentStates: data.agentStates,
258
+ url: data.frontend.url,
259
+ extensions: data.extensions,
260
+ metaEvents: data.metaEvents,
261
+ });
262
+ } catch (error) {
263
+ // Catch structured CopilotKit errors at the main mutation level and re-throw as GraphQL errors
264
+ if (isStructuredCopilotKitError(error) || (error as any)?.extensions?.visibility) {
265
+ throw new GraphQLError(error.message || "Agent error occurred", {
266
+ extensions: {
267
+ ...(error as any).extensions,
268
+ code: (error as any).code || (error as any).extensions?.code || "AGENT_ERROR",
269
+ originalError: error,
270
+ },
271
+ });
272
+ }
273
+ throw error; // Re-throw non-CopilotKit errors as-is
274
+ }
275
+
219
276
  const {
220
277
  eventSource,
221
278
  threadId = randomId(),
@@ -223,24 +280,7 @@ export class CopilotResolver {
223
280
  serverSideActions,
224
281
  actionInputsWithoutAgents,
225
282
  extensions,
226
- } = await copilotRuntime.processRuntimeRequest({
227
- serviceAdapter,
228
- messages: data.messages,
229
- actions: data.frontend.actions.filter(
230
- (action) => action.available !== ActionInputAvailability.disabled,
231
- ),
232
- threadId: data.threadId,
233
- runId: data.runId,
234
- publicApiKey: copilotCloudPublicApiKey,
235
- outputMessagesPromise,
236
- graphqlContext: ctx,
237
- forwardedParameters: data.forwardedParameters,
238
- agentSession: data.agentSession,
239
- agentStates: data.agentStates,
240
- url: data.frontend.url,
241
- extensions: data.extensions,
242
- metaEvents: data.metaEvents,
243
- });
283
+ } = runtimeResponse;
244
284
 
245
285
  logger.debug("Event source created, creating response");
246
286
  // run and process the event stream
@@ -338,12 +378,21 @@ export class CopilotResolver {
338
378
  }
339
379
  },
340
380
  error: (err) => {
341
- logger.error({ err }, "Error in meta events stream");
342
- responseStatus$.next(
343
- new UnknownErrorResponse({
344
- description: `An unknown error has occurred in the event stream`,
345
- }),
346
- );
381
+ // For structured CopilotKit errors, set proper error response status
382
+ if (err?.name?.includes("CopilotKit") || err?.extensions?.visibility) {
383
+ responseStatus$.next(
384
+ new UnknownErrorResponse({
385
+ description: err.message || "Agent error occurred",
386
+ }),
387
+ );
388
+ } else {
389
+ responseStatus$.next(
390
+ new UnknownErrorResponse({
391
+ description: `An unknown error has occurred in the event stream`,
392
+ }),
393
+ );
394
+ }
395
+
347
396
  eventStreamSubscription?.unsubscribe();
348
397
  stop();
349
398
  },
@@ -425,20 +474,20 @@ export class CopilotResolver {
425
474
  // create a sub stream that contains the message content
426
475
  const textMessageContentStream = eventStream.pipe(
427
476
  // skip until this message start event
428
- skipWhile((e) => e !== event),
477
+ skipWhile((e: RuntimeEvent) => e !== event),
429
478
  // take until the message end event
430
479
  takeWhile(
431
- (e) =>
480
+ (e: RuntimeEvent) =>
432
481
  !(
433
482
  e.type === RuntimeEventTypes.TextMessageEnd &&
434
- e.messageId == event.messageId
483
+ (e as any).messageId == event.messageId
435
484
  ),
436
485
  ),
437
486
  // filter out any other message events or message ids
438
487
  filter(
439
- (e) =>
488
+ (e: RuntimeEvent) =>
440
489
  e.type == RuntimeEventTypes.TextMessageContent &&
441
- e.messageId == event.messageId,
490
+ (e as any).messageId == event.messageId,
442
491
  ),
443
492
  );
444
493
 
@@ -520,20 +569,20 @@ export class CopilotResolver {
520
569
  case RuntimeEventTypes.ActionExecutionStart:
521
570
  logger.debug("Action execution start event received");
522
571
  const actionExecutionArgumentStream = eventStream.pipe(
523
- skipWhile((e) => e !== event),
572
+ skipWhile((e: RuntimeEvent) => e !== event),
524
573
  // take until the action execution end event
525
574
  takeWhile(
526
- (e) =>
575
+ (e: RuntimeEvent) =>
527
576
  !(
528
577
  e.type === RuntimeEventTypes.ActionExecutionEnd &&
529
- e.actionExecutionId == event.actionExecutionId
578
+ (e as any).actionExecutionId == event.actionExecutionId
530
579
  ),
531
580
  ),
532
581
  // filter out any other action execution events or action execution ids
533
582
  filter(
534
- (e) =>
583
+ (e: RuntimeEvent) =>
535
584
  e.type == RuntimeEventTypes.ActionExecutionArgs &&
536
- e.actionExecutionId == event.actionExecutionId,
585
+ (e as any).actionExecutionId == event.actionExecutionId,
537
586
  ),
538
587
  );
539
588
  const streamingArgumentsStatus = new Subject<typeof MessageStatusUnion>();
@@ -646,16 +695,30 @@ export class CopilotResolver {
646
695
  }
647
696
  },
648
697
  error: (err) => {
649
- logger.error({ err }, "Error in event stream");
650
-
651
- // If it's a structured CopilotKitError, stop the repeater with the error so frontend can handle it
698
+ // For structured CopilotKit errors, set proper error response status
652
699
  if (
653
700
  err instanceof CopilotKitError ||
654
- (err instanceof Error && err.name && err.name.includes("CopilotKit"))
701
+ err instanceof CopilotKitLowLevelError ||
702
+ (err instanceof Error && err.name && err.name.includes("CopilotKit")) ||
703
+ err?.extensions?.visibility
655
704
  ) {
705
+ responseStatus$.next(
706
+ new UnknownErrorResponse({
707
+ description: err.message || "Agent error occurred",
708
+ // Include original error information for frontend to extract
709
+ originalError: {
710
+ code: err.code || err.extensions?.code,
711
+ statusCode: err.statusCode || err.extensions?.statusCode,
712
+ severity: err.severity || err.extensions?.severity,
713
+ visibility: err.visibility || err.extensions?.visibility,
714
+ originalErrorType: err.originalErrorType || err.extensions?.originalErrorType,
715
+ extensions: err.extensions,
716
+ },
717
+ }),
718
+ );
656
719
  eventStreamSubscription?.unsubscribe();
657
720
  rejectOutputMessagesPromise(err);
658
- stopStreamingMessages(err); // Pass the error to stop the GraphQL stream with this error
721
+ stopStreamingMessages();
659
722
  return;
660
723
  }
661
724
 
@@ -10,9 +10,9 @@ import { CopilotKitAgentDiscoveryError } from "@copilotkit/shared";
10
10
  export class StateResolver {
11
11
  @Query(() => LoadAgentStateResponse)
12
12
  async loadAgentState(@Ctx() ctx: GraphQLContext, @Arg("data") data: LoadAgentStateInput) {
13
- const agents = await ctx._copilotkit.runtime.discoverAgentsFromEndpoints(ctx);
14
- const agent = agents.find((agent) => agent.name === data.agentName);
15
- if (!agent) {
13
+ const agents = await ctx._copilotkit.runtime.getAllAgents(ctx);
14
+ const hasAgent = agents.some((agent) => agent.name === data.agentName);
15
+ if (!hasAgent) {
16
16
  throw new CopilotKitAgentDiscoveryError({
17
17
  agentName: data.agentName,
18
18
  availableAgents: agents.map((a) => ({ name: a.name, id: a.name })),
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Error message configuration - Single source of truth for all error messages
3
+ *
4
+ * This centralized configuration system provides:
5
+ *
6
+ * 🎯 **Benefits:**
7
+ * - Single source of truth for all error messages
8
+ * - Easy content management without touching code
9
+ * - Consistent error messaging across the application
10
+ * - Ready for internationalization (i18n)
11
+ * - Type-safe configuration with TypeScript
12
+ * - Categorized errors for better handling
13
+ *
14
+ * 📝 **How to use:**
15
+ * 1. Add new error patterns to `errorPatterns` object
16
+ * 2. Use {context} placeholder for dynamic context injection
17
+ * 3. Specify category, severity, and actionable flags
18
+ * 4. Add fallback messages for error categories
19
+ *
20
+ * 🔧 **How to maintain:**
21
+ * - Content teams can update messages here without touching logic
22
+ * - Developers add new error patterns as needed
23
+ * - Use categories to group similar errors
24
+ * - Mark errors as actionable if users can fix them
25
+ *
26
+ * 🌍 **Future i18n support:**
27
+ * - Replace `message` string with `messages: { en: "...", es: "..." }`
28
+ * - Add locale parameter to helper functions
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * // Adding a new error pattern:
33
+ * "CUSTOM_ERROR": {
34
+ * message: "Custom error occurred in {context}. Please try again.",
35
+ * category: "unknown",
36
+ * severity: "error",
37
+ * actionable: true
38
+ * }
39
+ * ```
40
+ */
41
+
42
+ export interface ErrorPatternConfig {
43
+ message: string;
44
+ category: "network" | "connection" | "authentication" | "validation" | "unknown";
45
+ severity: "error" | "warning" | "info";
46
+ actionable: boolean;
47
+ }
48
+
49
+ export interface ErrorConfig {
50
+ errorPatterns: Record<string, ErrorPatternConfig>;
51
+ fallbacks: Record<string, string>;
52
+ contextTemplates: Record<string, string>;
53
+ }
54
+
55
+ export const errorConfig: ErrorConfig = {
56
+ errorPatterns: {
57
+ ECONNREFUSED: {
58
+ message:
59
+ "Connection refused - the agent service is not running or not accessible at the specified address. Please check that your agent is started and listening on the correct port.",
60
+ category: "network",
61
+ severity: "error",
62
+ actionable: true,
63
+ },
64
+ ENOTFOUND: {
65
+ message:
66
+ "Host not found - the agent service URL appears to be incorrect or the service is not accessible. Please verify the agent endpoint URL.",
67
+ category: "network",
68
+ severity: "error",
69
+ actionable: true,
70
+ },
71
+ ETIMEDOUT: {
72
+ message:
73
+ "Connection timeout - the agent service is taking too long to respond. This could indicate network issues or an overloaded agent service.",
74
+ category: "network",
75
+ severity: "warning",
76
+ actionable: true,
77
+ },
78
+ terminated: {
79
+ message:
80
+ "Agent {context} was unexpectedly terminated. This often indicates an error in the agent service (e.g., authentication failures, missing environment variables, or agent crashes). Check the agent logs for the root cause.",
81
+ category: "connection",
82
+ severity: "error",
83
+ actionable: true,
84
+ },
85
+ UND_ERR_SOCKET: {
86
+ message:
87
+ "Socket connection was closed unexpectedly. This typically indicates the agent service encountered an error and shut down the connection. Check the agent logs for the underlying cause.",
88
+ category: "connection",
89
+ severity: "error",
90
+ actionable: true,
91
+ },
92
+ other_side_closed: {
93
+ message:
94
+ "The agent service closed the connection unexpectedly. This usually indicates an error in the agent service. Check the agent logs for more details.",
95
+ category: "connection",
96
+ severity: "error",
97
+ actionable: true,
98
+ },
99
+ fetch_failed: {
100
+ message:
101
+ "Failed to connect to the agent service. Please verify the agent is running and the endpoint URL is correct.",
102
+ category: "network",
103
+ severity: "error",
104
+ actionable: true,
105
+ },
106
+ // Authentication patterns
107
+ "401": {
108
+ message:
109
+ "Authentication failed. Please check your API keys and ensure they are correctly configured.",
110
+ category: "authentication",
111
+ severity: "error",
112
+ actionable: true,
113
+ },
114
+ "api key": {
115
+ message:
116
+ "API key error detected. Please verify your API key is correct and has the necessary permissions.",
117
+ category: "authentication",
118
+ severity: "error",
119
+ actionable: true,
120
+ },
121
+ unauthorized: {
122
+ message: "Unauthorized access. Please check your authentication credentials.",
123
+ category: "authentication",
124
+ severity: "error",
125
+ actionable: true,
126
+ },
127
+ // Python-specific error patterns
128
+ AuthenticationError: {
129
+ message:
130
+ "OpenAI authentication failed. Please check your OPENAI_API_KEY environment variable or API key configuration.",
131
+ category: "authentication",
132
+ severity: "error",
133
+ actionable: true,
134
+ },
135
+ "Incorrect API key provided": {
136
+ message:
137
+ "OpenAI API key is invalid. Please verify your OPENAI_API_KEY is correct and active.",
138
+ category: "authentication",
139
+ severity: "error",
140
+ actionable: true,
141
+ },
142
+ RateLimitError: {
143
+ message:
144
+ "OpenAI rate limit exceeded. Please wait a moment and try again, or check your OpenAI usage limits.",
145
+ category: "network",
146
+ severity: "warning",
147
+ actionable: true,
148
+ },
149
+ InvalidRequestError: {
150
+ message:
151
+ "Invalid request to OpenAI API. Please check your request parameters and model configuration.",
152
+ category: "validation",
153
+ severity: "error",
154
+ actionable: true,
155
+ },
156
+ PermissionDeniedError: {
157
+ message:
158
+ "Permission denied for OpenAI API. Please check your API key permissions and billing status.",
159
+ category: "authentication",
160
+ severity: "error",
161
+ actionable: true,
162
+ },
163
+ NotFoundError: {
164
+ message: "OpenAI resource not found. Please check your model name and availability.",
165
+ category: "validation",
166
+ severity: "error",
167
+ actionable: true,
168
+ },
169
+ },
170
+ fallbacks: {
171
+ network:
172
+ "A network error occurred while connecting to the agent service. Please check your connection and ensure the agent service is running.",
173
+ connection:
174
+ "The connection to the agent service was lost unexpectedly. This may indicate an issue with the agent service.",
175
+ authentication: "Authentication failed. Please check your API keys and credentials.",
176
+ validation: "Invalid input or configuration. Please check your parameters and try again.",
177
+ unknown: "An unexpected error occurred. Please check the logs for more details.",
178
+ default: "An unexpected error occurred. Please check the logs for more details.",
179
+ },
180
+ contextTemplates: {
181
+ connection: "connection",
182
+ event_streaming_connection: "event streaming connection",
183
+ agent_streaming_connection: "agent streaming connection",
184
+ langgraph_agent_connection: "LangGraph agent connection",
185
+ },
186
+ };
187
+
188
+ /**
189
+ * Helper function to get error pattern configuration by key
190
+ */
191
+ export function getErrorPattern(key: string): ErrorPatternConfig | undefined {
192
+ return errorConfig.errorPatterns[key];
193
+ }
194
+
195
+ /**
196
+ * Helper function to get fallback message by category
197
+ */
198
+ export function getFallbackMessage(category: string): string {
199
+ return errorConfig.fallbacks[category] || errorConfig.fallbacks.default;
200
+ }
@@ -10,6 +10,7 @@ import { createYoga } from "graphql-yoga";
10
10
  import telemetry from "../telemetry-client";
11
11
  import { StateResolver } from "../../graphql/resolvers/state.resolver";
12
12
  import * as packageJson from "../../../package.json";
13
+ import { CopilotKitError, CopilotKitErrorCode } from "@copilotkit/shared";
13
14
 
14
15
  const logger = createLogger();
15
16
 
@@ -79,6 +80,9 @@ export type CommonConfig = {
79
80
  schema: ReturnType<typeof buildSchema>;
80
81
  plugins: Parameters<typeof createYoga>[0]["plugins"];
81
82
  context: (ctx: YogaInitialContext) => Promise<Partial<GraphQLContext>>;
83
+ maskedErrors: {
84
+ maskError: (error: any, message: string, isDev?: boolean) => any;
85
+ };
82
86
  };
83
87
 
84
88
  export function getCommonConfig(options: CreateCopilotRuntimeServerOptions): CommonConfig {
@@ -108,11 +112,50 @@ export function getCommonConfig(options: CreateCopilotRuntimeServerOptions): Com
108
112
  },
109
113
  });
110
114
 
115
+ // User error codes that should not be logged as server errors
116
+ const userErrorCodes = [
117
+ CopilotKitErrorCode.AGENT_NOT_FOUND,
118
+ CopilotKitErrorCode.API_NOT_FOUND,
119
+ CopilotKitErrorCode.REMOTE_ENDPOINT_NOT_FOUND,
120
+ CopilotKitErrorCode.CONFIGURATION_ERROR,
121
+ CopilotKitErrorCode.MISSING_PUBLIC_API_KEY_ERROR,
122
+ ];
123
+
111
124
  return {
112
125
  logging: createLogger({ component: "Yoga GraphQL", level: logLevel }),
113
126
  schema: buildSchema(),
114
127
  plugins: [useDeferStream(), addCustomHeaderPlugin],
115
128
  context: (ctx: YogaInitialContext): Promise<Partial<GraphQLContext>> =>
116
129
  createContext(ctx, options, contextLogger, options.properties),
130
+ // Suppress logging for user configuration errors
131
+ maskedErrors: {
132
+ maskError: (error: any, message: string, isDev?: boolean) => {
133
+ // Check if this is a user configuration error (could be wrapped in GraphQLError)
134
+ const originalError = error.originalError || error;
135
+ const extensions = error.extensions;
136
+ const errorCode = extensions?.code;
137
+
138
+ // Suppress logging for user errors based on error code
139
+ if (errorCode && userErrorCodes.includes(errorCode)) {
140
+ // Log user configuration errors at debug level instead
141
+ console.debug("User configuration error:", error.message);
142
+ return error;
143
+ }
144
+
145
+ // Check if the original error is a user error
146
+ if (
147
+ originalError instanceof CopilotKitError &&
148
+ userErrorCodes.includes(originalError.code)
149
+ ) {
150
+ // Log user configuration errors at debug level instead
151
+ console.debug("User configuration error:", error.message);
152
+ return error;
153
+ }
154
+
155
+ // For application errors, log normally and mask if needed
156
+ console.error("Application error:", error);
157
+ return error;
158
+ },
159
+ },
117
160
  };
118
161
  }
@@ -0,0 +1,169 @@
1
+ import { CopilotTraceEvent, CopilotRequestContext, CopilotTraceHandler } from "@copilotkit/shared";
2
+
3
+ describe("CopilotRuntime onTrace types", () => {
4
+ it("should have correct CopilotTraceEvent type structure", () => {
5
+ const traceEvent: CopilotTraceEvent = {
6
+ type: "error",
7
+ timestamp: Date.now(),
8
+ context: {
9
+ threadId: "test-123",
10
+ source: "runtime",
11
+ request: {
12
+ operation: "test-operation",
13
+ startTime: Date.now(),
14
+ },
15
+ technical: {},
16
+ metadata: {},
17
+ },
18
+ error: new Error("Test error"),
19
+ };
20
+
21
+ expect(traceEvent.type).toBe("error");
22
+ expect(traceEvent.timestamp).toBeGreaterThan(0);
23
+ expect(traceEvent.context.threadId).toBe("test-123");
24
+ expect(traceEvent.error).toBeInstanceOf(Error);
25
+ });
26
+
27
+ it("should have correct CopilotRequestContext type structure", () => {
28
+ const context: CopilotRequestContext = {
29
+ threadId: "test-thread-456",
30
+ runId: "test-run-789",
31
+ source: "runtime",
32
+ request: {
33
+ operation: "processRuntimeRequest",
34
+ method: "POST",
35
+ url: "http://localhost:3000/api/copilotkit",
36
+ startTime: Date.now(),
37
+ },
38
+ response: {
39
+ status: 200,
40
+ endTime: Date.now(),
41
+ latency: 1200,
42
+ },
43
+ agent: {
44
+ name: "test-agent",
45
+ nodeName: "test-node",
46
+ state: { step: 1 },
47
+ },
48
+ messages: {
49
+ input: [],
50
+ output: [],
51
+ messageCount: 2,
52
+ },
53
+ technical: {
54
+ userAgent: "Mozilla/5.0...",
55
+ host: "localhost:3000",
56
+ environment: "test",
57
+ version: "1.0.0",
58
+ stackTrace: "Error: Test\n at test.js:1:1",
59
+ },
60
+ performance: {
61
+ requestDuration: 1200,
62
+ streamingDuration: 800,
63
+ actionExecutionTime: 400,
64
+ memoryUsage: 45.2,
65
+ },
66
+ metadata: {
67
+ testFlag: true,
68
+ version: "1.0.0",
69
+ },
70
+ };
71
+
72
+ expect(context.threadId).toBe("test-thread-456");
73
+ expect(context.agent?.name).toBe("test-agent");
74
+ expect(context.messages?.messageCount).toBe(2);
75
+ expect(context.technical?.stackTrace).toContain("Error: Test");
76
+ expect(context.metadata?.testFlag).toBe(true);
77
+ });
78
+
79
+ it("should support all trace event types", () => {
80
+ const eventTypes: CopilotTraceEvent["type"][] = [
81
+ "error",
82
+ "request",
83
+ "response",
84
+ "agent_state",
85
+ "action",
86
+ "message",
87
+ "performance",
88
+ ];
89
+
90
+ eventTypes.forEach((type) => {
91
+ const event: CopilotTraceEvent = {
92
+ type,
93
+ timestamp: Date.now(),
94
+ context: {
95
+ threadId: `test-${type}`,
96
+ source: "runtime",
97
+ request: {
98
+ operation: "test",
99
+ startTime: Date.now(),
100
+ },
101
+ technical: {},
102
+ metadata: {},
103
+ },
104
+ };
105
+
106
+ expect(event.type).toBe(type);
107
+ });
108
+ });
109
+
110
+ describe("publicApiKey gating logic", () => {
111
+ type ShouldTrace = (onTrace?: CopilotTraceHandler, publicApiKey?: string) => boolean;
112
+
113
+ const shouldTrace: ShouldTrace = (onTrace, publicApiKey) => {
114
+ return Boolean(onTrace && publicApiKey);
115
+ };
116
+
117
+ it("should return true when both onTrace and publicApiKey are provided", () => {
118
+ const onTrace = jest.fn();
119
+ const result = shouldTrace(onTrace, "valid-api-key");
120
+ expect(result).toBe(true);
121
+ });
122
+
123
+ it("should return false when onTrace is missing", () => {
124
+ const result = shouldTrace(undefined, "valid-api-key");
125
+ expect(result).toBe(false);
126
+ });
127
+
128
+ it("should return false when publicApiKey is missing", () => {
129
+ const onTrace = jest.fn();
130
+ const result = shouldTrace(onTrace, undefined);
131
+ expect(result).toBe(false);
132
+ });
133
+
134
+ it("should return false when publicApiKey is empty string", () => {
135
+ const onTrace = jest.fn();
136
+ const result = shouldTrace(onTrace, "");
137
+ expect(result).toBe(false);
138
+ });
139
+
140
+ it("should return false when both are missing", () => {
141
+ const result = shouldTrace(undefined, undefined);
142
+ expect(result).toBe(false);
143
+ });
144
+
145
+ it("should extract publicApiKey from headers for both cloud and non-cloud requests", () => {
146
+ // Test the logic we just fixed in the GraphQL resolver
147
+ const mockHeaders = new Map([["x-copilotcloud-public-api-key", "test-key-123"]]);
148
+
149
+ // Simulate header extraction logic
150
+ const extractPublicApiKey = (headers: Map<string, string>, hasCloudConfig: boolean) => {
151
+ const publicApiKeyFromHeaders = headers.get("x-copilotcloud-public-api-key");
152
+ return publicApiKeyFromHeaders || null;
153
+ };
154
+
155
+ // Should work for cloud requests
156
+ const cloudKey = extractPublicApiKey(mockHeaders, true);
157
+ expect(cloudKey).toBe("test-key-123");
158
+
159
+ // Should also work for non-cloud requests (this was the bug)
160
+ const nonCloudKey = extractPublicApiKey(mockHeaders, false);
161
+ expect(nonCloudKey).toBe("test-key-123");
162
+
163
+ // Both should enable tracing when onTrace is present
164
+ const onTrace = jest.fn();
165
+ expect(shouldTrace(onTrace, cloudKey)).toBe(true);
166
+ expect(shouldTrace(onTrace, nonCloudKey)).toBe(true);
167
+ });
168
+ });
169
+ });