@copilotkit/runtime 1.56.0 → 1.56.2-canary.pin-to-send

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 (194) hide show
  1. package/dist/agent/index.cjs +2 -2
  2. package/dist/agent/index.cjs.map +1 -1
  3. package/dist/agent/index.d.cts.map +1 -1
  4. package/dist/agent/index.d.mts.map +1 -1
  5. package/dist/agent/index.mjs +2 -2
  6. package/dist/agent/index.mjs.map +1 -1
  7. package/dist/graphql/resolvers/copilot.resolver.cjs +2 -1
  8. package/dist/graphql/resolvers/copilot.resolver.cjs.map +1 -1
  9. package/dist/graphql/resolvers/copilot.resolver.mjs +2 -1
  10. package/dist/graphql/resolvers/copilot.resolver.mjs.map +1 -1
  11. package/dist/graphql/resolvers/resolve-message-id.cjs +19 -0
  12. package/dist/graphql/resolvers/resolve-message-id.cjs.map +1 -0
  13. package/dist/graphql/resolvers/resolve-message-id.mjs +18 -0
  14. package/dist/graphql/resolvers/resolve-message-id.mjs.map +1 -0
  15. package/dist/lib/integrations/node-http/index.cjs +4 -1
  16. package/dist/lib/integrations/node-http/index.cjs.map +1 -1
  17. package/dist/lib/integrations/node-http/index.d.cts.map +1 -1
  18. package/dist/lib/integrations/node-http/index.d.mts.map +1 -1
  19. package/dist/lib/integrations/node-http/index.mjs +4 -1
  20. package/dist/lib/integrations/node-http/index.mjs.map +1 -1
  21. package/dist/lib/runtime/copilot-runtime.cjs +15 -3
  22. package/dist/lib/runtime/copilot-runtime.cjs.map +1 -1
  23. package/dist/lib/runtime/copilot-runtime.d.cts.map +1 -1
  24. package/dist/lib/runtime/copilot-runtime.d.mts.map +1 -1
  25. package/dist/lib/runtime/copilot-runtime.mjs +15 -3
  26. package/dist/lib/runtime/copilot-runtime.mjs.map +1 -1
  27. package/dist/lib/runtime/mcp-tools-utils.cjs +21 -4
  28. package/dist/lib/runtime/mcp-tools-utils.cjs.map +1 -1
  29. package/dist/lib/runtime/mcp-tools-utils.d.cts.map +1 -1
  30. package/dist/lib/runtime/mcp-tools-utils.d.mts.map +1 -1
  31. package/dist/lib/runtime/mcp-tools-utils.mjs +21 -4
  32. package/dist/lib/runtime/mcp-tools-utils.mjs.map +1 -1
  33. package/dist/package.cjs +2 -2
  34. package/dist/package.mjs +2 -2
  35. package/dist/service-adapters/anthropic/anthropic-adapter.cjs +11 -3
  36. package/dist/service-adapters/anthropic/anthropic-adapter.cjs.map +1 -1
  37. package/dist/service-adapters/anthropic/anthropic-adapter.d.cts +6 -0
  38. package/dist/service-adapters/anthropic/anthropic-adapter.d.cts.map +1 -1
  39. package/dist/service-adapters/anthropic/anthropic-adapter.d.mts +6 -0
  40. package/dist/service-adapters/anthropic/anthropic-adapter.d.mts.map +1 -1
  41. package/dist/service-adapters/anthropic/anthropic-adapter.mjs +11 -3
  42. package/dist/service-adapters/anthropic/anthropic-adapter.mjs.map +1 -1
  43. package/dist/service-adapters/anthropic/utils.cjs +27 -1
  44. package/dist/service-adapters/anthropic/utils.cjs.map +1 -1
  45. package/dist/service-adapters/anthropic/utils.mjs +27 -1
  46. package/dist/service-adapters/anthropic/utils.mjs.map +1 -1
  47. package/dist/service-adapters/langchain/utils.cjs +1 -1
  48. package/dist/service-adapters/langchain/utils.cjs.map +1 -1
  49. package/dist/service-adapters/langchain/utils.mjs +1 -1
  50. package/dist/service-adapters/langchain/utils.mjs.map +1 -1
  51. package/dist/service-adapters/openai/openai-adapter.cjs +2 -1
  52. package/dist/service-adapters/openai/openai-adapter.cjs.map +1 -1
  53. package/dist/service-adapters/openai/openai-adapter.d.cts +6 -0
  54. package/dist/service-adapters/openai/openai-adapter.d.cts.map +1 -1
  55. package/dist/service-adapters/openai/openai-adapter.d.mts +6 -0
  56. package/dist/service-adapters/openai/openai-adapter.d.mts.map +1 -1
  57. package/dist/service-adapters/openai/openai-adapter.mjs +2 -1
  58. package/dist/service-adapters/openai/openai-adapter.mjs.map +1 -1
  59. package/dist/v2/runtime/core/debug-event-bus.cjs +36 -0
  60. package/dist/v2/runtime/core/debug-event-bus.cjs.map +1 -0
  61. package/dist/v2/runtime/core/debug-event-bus.d.cts +19 -0
  62. package/dist/v2/runtime/core/debug-event-bus.d.cts.map +1 -0
  63. package/dist/v2/runtime/core/debug-event-bus.d.mts +19 -0
  64. package/dist/v2/runtime/core/debug-event-bus.d.mts.map +1 -0
  65. package/dist/v2/runtime/core/debug-event-bus.mjs +35 -0
  66. package/dist/v2/runtime/core/debug-event-bus.mjs.map +1 -0
  67. package/dist/v2/runtime/core/fetch-handler.cjs +6 -0
  68. package/dist/v2/runtime/core/fetch-handler.cjs.map +1 -1
  69. package/dist/v2/runtime/core/fetch-handler.d.cts.map +1 -1
  70. package/dist/v2/runtime/core/fetch-handler.d.mts.map +1 -1
  71. package/dist/v2/runtime/core/fetch-handler.mjs +6 -0
  72. package/dist/v2/runtime/core/fetch-handler.mjs.map +1 -1
  73. package/dist/v2/runtime/core/fetch-router.cjs +1 -0
  74. package/dist/v2/runtime/core/fetch-router.cjs.map +1 -1
  75. package/dist/v2/runtime/core/fetch-router.mjs +1 -0
  76. package/dist/v2/runtime/core/fetch-router.mjs.map +1 -1
  77. package/dist/v2/runtime/core/hooks.cjs.map +1 -1
  78. package/dist/v2/runtime/core/hooks.d.cts +2 -0
  79. package/dist/v2/runtime/core/hooks.d.cts.map +1 -1
  80. package/dist/v2/runtime/core/hooks.d.mts +2 -0
  81. package/dist/v2/runtime/core/hooks.d.mts.map +1 -1
  82. package/dist/v2/runtime/core/hooks.mjs.map +1 -1
  83. package/dist/v2/runtime/core/middleware-sse-parser.cjs +5 -2
  84. package/dist/v2/runtime/core/middleware-sse-parser.cjs.map +1 -1
  85. package/dist/v2/runtime/core/middleware-sse-parser.mjs +5 -2
  86. package/dist/v2/runtime/core/middleware-sse-parser.mjs.map +1 -1
  87. package/dist/v2/runtime/core/runtime.cjs +5 -0
  88. package/dist/v2/runtime/core/runtime.cjs.map +1 -1
  89. package/dist/v2/runtime/core/runtime.d.cts +5 -0
  90. package/dist/v2/runtime/core/runtime.d.cts.map +1 -1
  91. package/dist/v2/runtime/core/runtime.d.mts +5 -0
  92. package/dist/v2/runtime/core/runtime.d.mts.map +1 -1
  93. package/dist/v2/runtime/core/runtime.mjs +5 -0
  94. package/dist/v2/runtime/core/runtime.mjs.map +1 -1
  95. package/dist/v2/runtime/handlers/handle-connect.cjs +2 -0
  96. package/dist/v2/runtime/handlers/handle-connect.cjs.map +1 -1
  97. package/dist/v2/runtime/handlers/handle-connect.mjs +2 -0
  98. package/dist/v2/runtime/handlers/handle-connect.mjs.map +1 -1
  99. package/dist/v2/runtime/handlers/handle-debug-events.cjs +33 -0
  100. package/dist/v2/runtime/handlers/handle-debug-events.cjs.map +1 -0
  101. package/dist/v2/runtime/handlers/handle-debug-events.mjs +32 -0
  102. package/dist/v2/runtime/handlers/handle-debug-events.mjs.map +1 -0
  103. package/dist/v2/runtime/handlers/handle-run.cjs +1 -0
  104. package/dist/v2/runtime/handlers/handle-run.cjs.map +1 -1
  105. package/dist/v2/runtime/handlers/handle-run.mjs +1 -0
  106. package/dist/v2/runtime/handlers/handle-run.mjs.map +1 -1
  107. package/dist/v2/runtime/handlers/intelligence/connect.cjs +32 -2
  108. package/dist/v2/runtime/handlers/intelligence/connect.cjs.map +1 -1
  109. package/dist/v2/runtime/handlers/intelligence/connect.mjs +31 -2
  110. package/dist/v2/runtime/handlers/intelligence/connect.mjs.map +1 -1
  111. package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.cjs +5 -1
  112. package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.cjs.map +1 -1
  113. package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.mjs +5 -1
  114. package/dist/v2/runtime/handlers/shared/resolve-intelligence-user.mjs.map +1 -1
  115. package/dist/v2/runtime/handlers/shared/sse-response.cjs +21 -1
  116. package/dist/v2/runtime/handlers/shared/sse-response.cjs.map +1 -1
  117. package/dist/v2/runtime/handlers/shared/sse-response.mjs +21 -1
  118. package/dist/v2/runtime/handlers/shared/sse-response.mjs.map +1 -1
  119. package/dist/v2/runtime/handlers/sse/connect.cjs +3 -1
  120. package/dist/v2/runtime/handlers/sse/connect.cjs.map +1 -1
  121. package/dist/v2/runtime/handlers/sse/connect.mjs +3 -1
  122. package/dist/v2/runtime/handlers/sse/connect.mjs.map +1 -1
  123. package/dist/v2/runtime/handlers/sse/run.cjs +3 -1
  124. package/dist/v2/runtime/handlers/sse/run.cjs.map +1 -1
  125. package/dist/v2/runtime/handlers/sse/run.mjs +3 -1
  126. package/dist/v2/runtime/handlers/sse/run.mjs.map +1 -1
  127. package/dist/v2/runtime/intelligence-platform/client.cjs +2 -7
  128. package/dist/v2/runtime/intelligence-platform/client.cjs.map +1 -1
  129. package/dist/v2/runtime/intelligence-platform/client.d.cts +1 -4
  130. package/dist/v2/runtime/intelligence-platform/client.d.cts.map +1 -1
  131. package/dist/v2/runtime/intelligence-platform/client.d.mts +1 -4
  132. package/dist/v2/runtime/intelligence-platform/client.d.mts.map +1 -1
  133. package/dist/v2/runtime/intelligence-platform/client.mjs +2 -7
  134. package/dist/v2/runtime/intelligence-platform/client.mjs.map +1 -1
  135. package/dist/v2/runtime/runner/intelligence.cjs +17 -5
  136. package/dist/v2/runtime/runner/intelligence.cjs.map +1 -1
  137. package/dist/v2/runtime/runner/intelligence.d.cts +1 -0
  138. package/dist/v2/runtime/runner/intelligence.d.cts.map +1 -1
  139. package/dist/v2/runtime/runner/intelligence.d.mts +1 -0
  140. package/dist/v2/runtime/runner/intelligence.d.mts.map +1 -1
  141. package/dist/v2/runtime/runner/intelligence.mjs +17 -5
  142. package/dist/v2/runtime/runner/intelligence.mjs.map +1 -1
  143. package/package.json +3 -3
  144. package/src/agent/__tests__/provider-id-collision.test.ts +195 -0
  145. package/src/agent/index.ts +19 -11
  146. package/src/agents/langgraph/__tests__/event-source.test.ts +256 -0
  147. package/src/graphql/resolvers/__tests__/resolve-message-id.test.ts +25 -0
  148. package/src/graphql/resolvers/copilot.resolver.ts +2 -1
  149. package/src/graphql/resolvers/resolve-message-id.ts +14 -0
  150. package/src/lib/integrations/node-http/__tests__/request-duck-type.test.ts +66 -0
  151. package/src/lib/integrations/node-http/index.ts +15 -1
  152. package/src/lib/runtime/__tests__/handle-service-adapter.test.ts +108 -0
  153. package/src/lib/runtime/__tests__/mcp-tools-utils.test.ts +30 -1
  154. package/src/lib/runtime/__tests__/on-after-request.test.ts +122 -0
  155. package/src/lib/runtime/__tests__/retry-utils.test.ts +137 -0
  156. package/src/lib/runtime/agent-integrations/langgraph/__tests__/dispatch-event-filtering.test.ts +190 -0
  157. package/src/lib/runtime/copilot-runtime.ts +36 -7
  158. package/src/lib/runtime/mcp-tools-utils.ts +41 -6
  159. package/src/lib/runtime/retry-utils.ts +41 -1
  160. package/src/service-adapters/anthropic/anthropic-adapter.ts +22 -2
  161. package/src/service-adapters/anthropic/utils.ts +60 -1
  162. package/src/service-adapters/langchain/utils.ts +1 -1
  163. package/src/service-adapters/openai/openai-adapter.ts +14 -1
  164. package/src/v2/runtime/__tests__/fetch-router.test.ts +22 -0
  165. package/src/v2/runtime/__tests__/handle-connect.test.ts +58 -5
  166. package/src/v2/runtime/__tests__/handle-run.test.ts +31 -4
  167. package/src/v2/runtime/__tests__/handle-threads.test.ts +66 -4
  168. package/src/v2/runtime/__tests__/integration/node-servers.integration.test.ts +19 -0
  169. package/src/v2/runtime/__tests__/integration/suites/debug-events.suite.ts +253 -0
  170. package/src/v2/runtime/__tests__/middleware-sse-parser.test.ts +50 -0
  171. package/src/v2/runtime/__tests__/runtime.test.ts +3 -1
  172. package/src/v2/runtime/core/__tests__/debug-event-bus.test.ts +156 -0
  173. package/src/v2/runtime/core/debug-event-bus.ts +45 -0
  174. package/src/v2/runtime/core/fetch-handler.ts +4 -0
  175. package/src/v2/runtime/core/fetch-router.ts +11 -0
  176. package/src/v2/runtime/core/hooks.ts +2 -1
  177. package/src/v2/runtime/core/middleware-sse-parser.ts +12 -2
  178. package/src/v2/runtime/core/runtime.ts +12 -0
  179. package/src/v2/runtime/handlers/__tests__/handle-debug-events.test.ts +176 -0
  180. package/src/v2/runtime/handlers/handle-connect.ts +2 -0
  181. package/src/v2/runtime/handlers/handle-debug-events.ts +52 -0
  182. package/src/v2/runtime/handlers/handle-run.ts +1 -0
  183. package/src/v2/runtime/handlers/intelligence/connect.ts +58 -1
  184. package/src/v2/runtime/handlers/shared/resolve-intelligence-user.ts +4 -1
  185. package/src/v2/runtime/handlers/shared/sse-response.ts +46 -0
  186. package/src/v2/runtime/handlers/sse/__tests__/sse-connect-agent-id.test.ts +71 -0
  187. package/src/v2/runtime/handlers/sse/connect.ts +6 -0
  188. package/src/v2/runtime/handlers/sse/run.ts +4 -0
  189. package/src/v2/runtime/intelligence-platform/__tests__/client.test.ts +13 -11
  190. package/src/v2/runtime/intelligence-platform/client.ts +3 -11
  191. package/src/v2/runtime/runner/__tests__/intelligence-runner.test.ts +51 -1
  192. package/src/v2/runtime/runner/intelligence.ts +27 -9
  193. package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +268 -0
  194. package/tests/service-adapters/anthropic/utils-token-trimming.test.ts +301 -0
@@ -85,30 +85,65 @@ export function extractParametersFromSchema(
85
85
  }
86
86
  }
87
87
 
88
- // Handle enums
88
+ // Handle enums — preserve as structured data for Zod conversion
89
+ let enumValues: string[] | undefined;
89
90
  if (paramDef.enum && Array.isArray(paramDef.enum)) {
90
- const enumValues = paramDef.enum.join(" | ");
91
+ enumValues = paramDef.enum.map(String);
92
+ const enumDisplay = enumValues.join(" | ");
91
93
  description =
92
94
  description +
93
95
  (description ? " " : "") +
94
- `Allowed values: ${enumValues}`;
96
+ `Allowed values: ${enumDisplay}`;
95
97
  }
96
98
 
97
- // Handle objects with properties
99
+ // Handle objects with properties — recurse to preserve nested structure
100
+ let attributes: Parameter[] | undefined;
98
101
  if (type === "object" && paramDef.properties) {
99
102
  const objectProperties = Object.keys(paramDef.properties).join(", ");
100
103
  description =
101
104
  description +
102
105
  (description ? " " : "") +
103
106
  `Object with properties: ${objectProperties}`;
107
+ // Recursively extract nested parameters
108
+ attributes = extractParametersFromSchema({
109
+ parameters: {
110
+ properties: paramDef.properties,
111
+ required: paramDef.required || [],
112
+ },
113
+ });
104
114
  }
105
115
 
106
- parameters.push({
116
+ // Handle object arrays — recurse into item schema
117
+ if (type === "array" && paramDef.items?.type === "object" && paramDef.items?.properties) {
118
+ attributes = extractParametersFromSchema({
119
+ parameters: {
120
+ properties: paramDef.items.properties,
121
+ required: paramDef.items.required || [],
122
+ },
123
+ });
124
+ }
125
+
126
+ const param: any = {
107
127
  name: paramName,
108
128
  type: type,
109
129
  description: description,
110
130
  required: requiredParams.has(paramName),
111
- });
131
+ };
132
+
133
+ // Preserve enum values for string parameters
134
+ if (type === "string" && enumValues) {
135
+ param.enum = enumValues;
136
+ }
137
+
138
+ // Preserve nested attributes for object and object[] types
139
+ if (attributes && attributes.length > 0) {
140
+ param.attributes = attributes;
141
+ if (type === "array") {
142
+ param.type = "object[]";
143
+ }
144
+ }
145
+
146
+ parameters.push(param);
112
147
  }
113
148
  }
114
149
 
@@ -7,6 +7,8 @@ export const RETRY_CONFIG = {
7
7
  maxDelayMs: 5000,
8
8
  // HTTP status codes that should be retried
9
9
  retryableStatusCodes: [502, 503, 504, 408, 429],
10
+ // Maximum Retry-After value (in seconds) we're willing to honor
11
+ maxRetryAfterSeconds: 60,
10
12
  // Network error patterns that should be retried
11
13
  retryableErrorMessages: [
12
14
  "fetch failed",
@@ -44,6 +46,28 @@ export function sleep(ms: number): Promise<void> {
44
46
  return new Promise((resolve) => setTimeout(resolve, ms));
45
47
  }
46
48
 
49
+ // Parse the Retry-After header value into milliseconds.
50
+ // Returns undefined if the header is missing or unparseable.
51
+ export function parseRetryAfter(response: Response): number | undefined {
52
+ const retryAfter = response.headers.get("Retry-After");
53
+ if (!retryAfter) return undefined;
54
+
55
+ // Try as seconds (integer)
56
+ const seconds = Number(retryAfter);
57
+ if (!Number.isNaN(seconds) && seconds >= 0) {
58
+ return seconds * 1000;
59
+ }
60
+
61
+ // Try as HTTP-date
62
+ const date = Date.parse(retryAfter);
63
+ if (!Number.isNaN(date)) {
64
+ const delayMs = date - Date.now();
65
+ return delayMs > 0 ? delayMs : 0;
66
+ }
67
+
68
+ return undefined;
69
+ }
70
+
47
71
  // Calculate exponential backoff delay
48
72
  export function calculateDelay(attempt: number): number {
49
73
  const delay = RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt);
@@ -67,7 +91,23 @@ export async function fetchWithRetry(
67
91
  isRetryableError(null, response) &&
68
92
  attempt < RETRY_CONFIG.maxRetries
69
93
  ) {
70
- const delay = calculateDelay(attempt);
94
+ let delay = calculateDelay(attempt);
95
+
96
+ // Honor Retry-After header on 429 responses
97
+ if (response.status === 429) {
98
+ const retryAfterMs = parseRetryAfter(response);
99
+ if (retryAfterMs !== undefined) {
100
+ const maxMs = RETRY_CONFIG.maxRetryAfterSeconds * 1000;
101
+ if (retryAfterMs > maxMs) {
102
+ throw new Error(
103
+ `Server requested Retry-After of ${Math.ceil(retryAfterMs / 1000)}s ` +
104
+ `which exceeds the maximum of ${RETRY_CONFIG.maxRetryAfterSeconds}s`,
105
+ );
106
+ }
107
+ delay = retryAfterMs;
108
+ }
109
+ }
110
+
71
111
  logger?.warn(
72
112
  `Request to ${url} failed with status ${response.status}. ` +
73
113
  `Retrying attempt ${attempt + 1}/${RETRY_CONFIG.maxRetries + 1} in ${delay}ms.`,
@@ -70,12 +70,19 @@ export interface AnthropicAdapterParams {
70
70
  * See: https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
71
71
  */
72
72
  promptCaching?: AnthropicPromptCachingConfig;
73
+
74
+ /**
75
+ * Optional maximum input token limit. Overrides the default limit
76
+ * used when trimming messages to fit the context window.
77
+ */
78
+ maxInputTokens?: number;
73
79
  }
74
80
 
75
81
  export class AnthropicAdapter implements CopilotServiceAdapter {
76
82
  public model: string = DEFAULT_MODEL;
77
83
  public provider = "anthropic";
78
84
  private promptCaching: AnthropicPromptCachingConfig;
85
+ private maxInputTokens?: number;
79
86
 
80
87
  private _anthropic: Anthropic;
81
88
  public get anthropic(): Anthropic {
@@ -94,6 +101,7 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
94
101
  this.model = params.model;
95
102
  }
96
103
  this.promptCaching = params?.promptCaching || { enabled: false };
104
+ this.maxInputTokens = params?.maxInputTokens;
97
105
  }
98
106
 
99
107
  getLanguageModel(): LanguageModel {
@@ -244,6 +252,7 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
244
252
  forwardedParameters,
245
253
  } = request;
246
254
  const tools = actions.map(convertActionInputToAnthropicTool);
255
+ const knownActionNames = new Set(actions.map((a) => a.name));
247
256
 
248
257
  const messages = [...rawMessages];
249
258
 
@@ -322,6 +331,7 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
322
331
  anthropicMessages,
323
332
  tools,
324
333
  model,
334
+ this.maxInputTokens,
325
335
  );
326
336
 
327
337
  // Apply prompt caching if enabled
@@ -350,7 +360,7 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
350
360
  system: cachedSystemPrompt,
351
361
  model: this.model,
352
362
  messages: cachedMessages,
353
- max_tokens: forwardedParameters?.maxTokens || 1024,
363
+ max_tokens: forwardedParameters?.maxTokens || 4096,
354
364
  ...(forwardedParameters?.temperature
355
365
  ? { temperature: forwardedParameters.temperature }
356
366
  : {}),
@@ -375,12 +385,18 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
375
385
  if (chunk.type === "message_start") {
376
386
  currentMessageId = chunk.message.id;
377
387
  } else if (chunk.type === "content_block_start") {
378
- hasReceivedContent = true;
379
388
  if (chunk.content_block.type === "text") {
389
+ hasReceivedContent = true;
380
390
  didOutputText = false;
381
391
  filterThinkingTextBuffer.reset();
382
392
  mode = "message";
383
393
  } else if (chunk.content_block.type === "tool_use") {
394
+ if (!knownActionNames.has(chunk.content_block.name)) {
395
+ // Unknown tool - skip execution to prevent crashes
396
+ mode = null;
397
+ continue;
398
+ }
399
+ hasReceivedContent = true;
384
400
  currentToolCallId = chunk.content_block.id;
385
401
  eventStream$.sendActionExecutionStart({
386
402
  actionExecutionId: currentToolCallId,
@@ -390,6 +406,10 @@ export class AnthropicAdapter implements CopilotServiceAdapter {
390
406
  mode = "function";
391
407
  }
392
408
  } else if (chunk.type === "content_block_delta") {
409
+ if (mode === null) {
410
+ // Skip deltas for unknown/skipped content blocks
411
+ continue;
412
+ }
393
413
  if (chunk.delta.type === "text_delta") {
394
414
  const text = filterThinkingTextBuffer.onTextChunk(
395
415
  chunk.delta.text,
@@ -49,7 +49,66 @@ export function limitMessagesToTokenCount(
49
49
  maxTokens -= numTokens;
50
50
  }
51
51
 
52
- return result;
52
+ // Post-process: remove orphaned tool_result and tool_use blocks.
53
+ // Token trimming may have removed the assistant message containing tool_use
54
+ // while keeping the user message with tool_result (or vice versa),
55
+ // which Anthropic rejects.
56
+
57
+ // Collect all tool_use IDs from assistant messages
58
+ const toolUseIds = new Set<string>();
59
+ for (const msg of result) {
60
+ if (msg.role === "assistant" && Array.isArray(msg.content)) {
61
+ for (const block of msg.content) {
62
+ if (block.type === "tool_use") {
63
+ toolUseIds.add(block.id);
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ // Collect all tool_result IDs from user messages
70
+ const toolResultIds = new Set<string>();
71
+ for (const msg of result) {
72
+ if (msg.role === "user" && Array.isArray(msg.content)) {
73
+ for (const block of msg.content) {
74
+ if (block.type === "tool_result") {
75
+ toolResultIds.add(block.tool_use_id);
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ // Filter orphaned blocks without mutating the original messages
82
+ const filtered: any[] = [];
83
+ for (const msg of result) {
84
+ if (msg.role === "user" && Array.isArray(msg.content)) {
85
+ const remaining = msg.content.filter(
86
+ (block: any) =>
87
+ block.type !== "tool_result" || toolUseIds.has(block.tool_use_id),
88
+ );
89
+ if (remaining.length === 0) continue;
90
+ if (remaining.length !== msg.content.length) {
91
+ filtered.push({ ...msg, content: remaining });
92
+ } else {
93
+ filtered.push(msg);
94
+ }
95
+ } else if (msg.role === "assistant" && Array.isArray(msg.content)) {
96
+ const remaining = msg.content.filter(
97
+ (block: any) =>
98
+ block.type !== "tool_use" || toolResultIds.has(block.id),
99
+ );
100
+ if (remaining.length === 0) continue;
101
+ if (remaining.length !== msg.content.length) {
102
+ filtered.push({ ...msg, content: remaining });
103
+ } else {
104
+ filtered.push(msg);
105
+ }
106
+ } else {
107
+ filtered.push(msg);
108
+ }
109
+ }
110
+
111
+ return filtered;
53
112
  }
54
113
 
55
114
  const MAX_TOKENS = 128000;
@@ -269,7 +269,7 @@ export async function streamLangChainResponse({
269
269
  });
270
270
  } else if (content) {
271
271
  mode = "message";
272
- currentMessageId = value.lc_kwargs?.id || randomId();
272
+ currentMessageId = randomId();
273
273
  eventStream$.sendTextMessageStart({ messageId: currentMessageId });
274
274
  }
275
275
  }
@@ -97,6 +97,12 @@ export interface OpenAIAdapterParams {
97
97
  * @default false
98
98
  */
99
99
  keepSystemRole?: boolean;
100
+
101
+ /**
102
+ * Optional maximum input token limit. Overrides the default model-based limit
103
+ * used when trimming messages to fit the context window.
104
+ */
105
+ maxInputTokens?: number;
100
106
  }
101
107
 
102
108
  export class OpenAIAdapter implements CopilotServiceAdapter {
@@ -106,6 +112,7 @@ export class OpenAIAdapter implements CopilotServiceAdapter {
106
112
  private disableParallelToolCalls: boolean = false;
107
113
  private _openai: OpenAI;
108
114
  private keepSystemRole: boolean = false;
115
+ private maxInputTokens?: number;
109
116
 
110
117
  public get openai(): OpenAI {
111
118
  return this._openai;
@@ -125,6 +132,7 @@ export class OpenAIAdapter implements CopilotServiceAdapter {
125
132
  }
126
133
  this.disableParallelToolCalls = params?.disableParallelToolCalls || false;
127
134
  this.keepSystemRole = params?.keepSystemRole ?? false;
135
+ this.maxInputTokens = params?.maxInputTokens;
128
136
  }
129
137
 
130
138
  getLanguageModel(): LanguageModel {
@@ -192,7 +200,12 @@ export class OpenAIAdapter implements CopilotServiceAdapter {
192
200
  let openaiMessages = filteredMessages.map((m) =>
193
201
  convertMessageToOpenAIMessage(m, { keepSystemRole: this.keepSystemRole }),
194
202
  );
195
- openaiMessages = limitMessagesToTokenCount(openaiMessages, tools, model);
203
+ openaiMessages = limitMessagesToTokenCount(
204
+ openaiMessages,
205
+ tools,
206
+ model,
207
+ this.maxInputTokens,
208
+ );
196
209
 
197
210
  let toolChoice: any = forwardedParameters?.toolChoice;
198
211
  if (forwardedParameters?.toolChoice === "function") {
@@ -135,6 +135,11 @@ describe("fetch-router", () => {
135
135
  const result = matchRoute("/info", "/");
136
136
  expect(result).toEqual({ method: "info" });
137
137
  });
138
+
139
+ it("matches GET /cpk-debug-events", () => {
140
+ const result = matchRoute("/api/copilotkit/cpk-debug-events", basePath);
141
+ expect(result).toEqual({ method: "cpk-debug-events" });
142
+ });
138
143
  });
139
144
 
140
145
  describe("without basePath (suffix matching)", () => {
@@ -204,5 +209,22 @@ describe("fetch-router", () => {
204
209
  const result = matchRoute("/api/v2/copilotkit/agent/a1/run");
205
210
  expect(result).toEqual({ method: "agent/run", agentId: "a1" });
206
211
  });
212
+
213
+ it("matches /cpk-debug-events suffix", () => {
214
+ const result = matchRoute("/api/copilotkit/cpk-debug-events");
215
+ expect(result).toEqual({ method: "cpk-debug-events" });
216
+ });
217
+
218
+ it("matches bare /cpk-debug-events", () => {
219
+ const result = matchRoute("/cpk-debug-events");
220
+ expect(result).toEqual({ method: "cpk-debug-events" });
221
+ });
222
+ });
223
+
224
+ describe("cpk-debug-events route with basePath", () => {
225
+ it("matches /cpk-debug-events with /api basePath", () => {
226
+ const result = matchRoute("/api/cpk-debug-events", "/api");
227
+ expect(result).toEqual({ method: "cpk-debug-events" });
228
+ });
207
229
  });
208
230
  });
@@ -228,7 +228,9 @@ describe("handleConnectAgent", () => {
228
228
  afterRequestMiddleware: undefined,
229
229
  runner,
230
230
  mode: "intelligence",
231
- identifyUser: vi.fn().mockResolvedValue({ id: "user-1" }),
231
+ identifyUser: vi
232
+ .fn()
233
+ .mockResolvedValue({ id: "user-1", name: "User One" }),
232
234
  intelligence: platform,
233
235
  } as unknown as CopilotRuntime;
234
236
  };
@@ -262,6 +264,7 @@ describe("handleConnectAgent", () => {
262
264
  expect(platform.ɵconnectThread).toHaveBeenCalledWith({
263
265
  threadId: "thread-1",
264
266
  userId: "user-1",
267
+ runId: "run-1",
265
268
  lastSeenEventId: null,
266
269
  });
267
270
  });
@@ -271,7 +274,15 @@ describe("handleConnectAgent", () => {
271
274
  ɵconnectThread: vi.fn().mockResolvedValue({
272
275
  mode: "bootstrap",
273
276
  latestEventId: "event-2",
274
- events: [{ type: "MESSAGES_SNAPSHOT", messages: [] }],
277
+ events: [
278
+ {
279
+ type: "RUN_STARTED",
280
+ threadId: "thread-1",
281
+ run_id: "backend-run-1",
282
+ input: { messages: [] },
283
+ },
284
+ { type: "RUN_FINISHED" },
285
+ ],
275
286
  }),
276
287
  };
277
288
  const runtime = createIntelligenceRuntime(platform);
@@ -287,7 +298,23 @@ describe("handleConnectAgent", () => {
287
298
  expect(body).toEqual({
288
299
  mode: "bootstrap",
289
300
  latestEventId: "event-2",
290
- events: [{ type: "MESSAGES_SNAPSHOT", messages: [] }],
301
+ events: [
302
+ {
303
+ type: "RUN_STARTED",
304
+ threadId: "thread-1",
305
+ runId: "run-1",
306
+ input: {
307
+ messages: [],
308
+ threadId: "thread-1",
309
+ runId: "run-1",
310
+ },
311
+ },
312
+ {
313
+ type: "RUN_FINISHED",
314
+ threadId: "thread-1",
315
+ runId: "run-1",
316
+ },
317
+ ],
291
318
  });
292
319
  });
293
320
 
@@ -310,6 +337,7 @@ describe("handleConnectAgent", () => {
310
337
  expect(platform.ɵconnectThread).toHaveBeenCalledWith({
311
338
  threadId: "thread-1",
312
339
  userId: "user-1",
340
+ runId: "run-1",
313
341
  lastSeenEventId: null,
314
342
  });
315
343
  });
@@ -349,6 +377,7 @@ describe("handleConnectAgent", () => {
349
377
  expect(platform.ɵconnectThread).toHaveBeenCalledWith({
350
378
  threadId: "thread-1",
351
379
  userId: "user-1",
380
+ runId: "run-1",
352
381
  lastSeenEventId: "event-9",
353
382
  });
354
383
  });
@@ -357,7 +386,9 @@ describe("handleConnectAgent", () => {
357
386
  const platform = {
358
387
  ɵconnectThread: vi.fn().mockResolvedValue(null),
359
388
  };
360
- const identifyUser = vi.fn().mockResolvedValue({ id: "resolved-user" });
389
+ const identifyUser = vi
390
+ .fn()
391
+ .mockResolvedValue({ id: "resolved-user", name: "Resolved User" });
361
392
  const runtime = createIntelligenceRuntime(platform);
362
393
  runtime.identifyUser = identifyUser;
363
394
  const request = createConnectRequest(
@@ -377,6 +408,7 @@ describe("handleConnectAgent", () => {
377
408
  expect(platform.ɵconnectThread).toHaveBeenCalledWith({
378
409
  threadId: "thread-1",
379
410
  userId: "resolved-user",
411
+ runId: "run-1",
380
412
  lastSeenEventId: "event-9",
381
413
  });
382
414
  });
@@ -386,7 +418,28 @@ describe("handleConnectAgent", () => {
386
418
  ɵconnectThread: vi.fn(),
387
419
  };
388
420
  const runtime = createIntelligenceRuntime(platform);
389
- runtime.identifyUser = vi.fn().mockResolvedValue({ id: "" });
421
+ runtime.identifyUser = vi
422
+ .fn()
423
+ .mockResolvedValue({ id: "", name: "User" });
424
+
425
+ const response = await handleConnectAgent({
426
+ runtime,
427
+ request: createConnectRequest(),
428
+ agentId: "my-agent",
429
+ });
430
+
431
+ expect(response.status).toBe(400);
432
+ expect(platform.ɵconnectThread).not.toHaveBeenCalled();
433
+ });
434
+
435
+ it("returns 400 when identifyUser returns an invalid name", async () => {
436
+ const platform = {
437
+ ɵconnectThread: vi.fn(),
438
+ };
439
+ const runtime = createIntelligenceRuntime(platform);
440
+ runtime.identifyUser = vi
441
+ .fn()
442
+ .mockResolvedValue({ id: "user-1", name: "" });
390
443
 
391
444
  const response = await handleConnectAgent({
392
445
  runtime,
@@ -298,7 +298,9 @@ describe("handleRunAgent", () => {
298
298
  generateThreadNames?: boolean;
299
299
  identifyUser?: (
300
300
  request: Request,
301
- ) => { id: string } | Promise<{ id: string }>;
301
+ ) =>
302
+ | { id: string; name: string }
303
+ | Promise<{ id: string; name: string }>;
302
304
  },
303
305
  ) => {
304
306
  const runner = Object.create(IntelligenceAgentRunner.prototype);
@@ -318,7 +320,8 @@ describe("handleRunAgent", () => {
318
320
  generateThreadNames: options?.generateThreadNames ?? false,
319
321
  intelligence: platform,
320
322
  identifyUser:
321
- options?.identifyUser ?? vi.fn().mockResolvedValue({ id: "user-1" }),
323
+ options?.identifyUser ??
324
+ vi.fn().mockResolvedValue({ id: "user-1", name: "User One" }),
322
325
  } as unknown as CopilotRuntime;
323
326
  };
324
327
 
@@ -395,7 +398,9 @@ describe("handleRunAgent", () => {
395
398
  .fn()
396
399
  .mockResolvedValue({ joinToken: "jt-123", joinCode: "jc-123" }),
397
400
  };
398
- const identifyUser = vi.fn().mockResolvedValue({ id: "resolved-user" });
401
+ const identifyUser = vi
402
+ .fn()
403
+ .mockResolvedValue({ id: "resolved-user", name: "Resolved User" });
399
404
  const runtime = createIntelligenceRuntime(agent, platform, {
400
405
  identifyUser,
401
406
  });
@@ -870,7 +875,29 @@ describe("handleRunAgent", () => {
870
875
  ɵacquireThreadLock: vi.fn(),
871
876
  };
872
877
  const runtime = createIntelligenceRuntime(agent, platform, {
873
- identifyUser: vi.fn().mockResolvedValue({ id: "" }),
878
+ identifyUser: vi.fn().mockResolvedValue({ id: "", name: "User" }),
879
+ });
880
+
881
+ const response = await handleRunAgent({
882
+ runtime,
883
+ request: createRunRequest(),
884
+ agentId: "my-agent",
885
+ });
886
+
887
+ expect(response.status).toBe(400);
888
+ expect(platform.getOrCreateThread).not.toHaveBeenCalled();
889
+ expect(platform.ɵacquireThreadLock).not.toHaveBeenCalled();
890
+ });
891
+
892
+ it("returns 400 when identifyUser returns an invalid name", async () => {
893
+ const agent = createAgentForIntelligence();
894
+ const platform = {
895
+ getOrCreateThread: vi.fn(),
896
+ getThreadMessages: vi.fn(),
897
+ ɵacquireThreadLock: vi.fn(),
898
+ };
899
+ const runtime = createIntelligenceRuntime(agent, platform, {
900
+ identifyUser: vi.fn().mockResolvedValue({ id: "user-1", name: "" }),
874
901
  });
875
902
 
876
903
  const response = await handleRunAgent({
@@ -10,12 +10,13 @@ import {
10
10
  import { CopilotRuntime } from "../core/runtime";
11
11
 
12
12
  describe("thread handlers", () => {
13
- const createIdentifyUser = () => vi.fn().mockResolvedValue({ id: "user-1" });
13
+ const createIdentifyUser = () =>
14
+ vi.fn().mockResolvedValue({ id: "user-1", name: "User One" });
14
15
 
15
16
  const createIntelligenceRuntime = (options?: {
16
17
  identifyUser?: (
17
18
  request: Request,
18
- ) => { id: string } | Promise<{ id: string }>;
19
+ ) => { id: string; name: string } | Promise<{ id: string; name: string }>;
19
20
  intelligence?: Record<string, unknown>;
20
21
  }) =>
21
22
  ({
@@ -92,7 +93,25 @@ describe("thread handlers", () => {
92
93
  };
93
94
  const runtime = createIntelligenceRuntime({
94
95
  intelligence,
95
- identifyUser: vi.fn().mockResolvedValue({ id: "" }),
96
+ identifyUser: vi.fn().mockResolvedValue({ id: "", name: "User" }),
97
+ });
98
+
99
+ const response = await handleListThreads({
100
+ runtime,
101
+ request: new Request("https://example.com/threads?agentId=agent-1"),
102
+ });
103
+
104
+ expect(response.status).toBe(400);
105
+ expect(intelligence.listThreads).not.toHaveBeenCalled();
106
+ });
107
+
108
+ it("returns 400 when identifyUser returns an invalid name for thread list", async () => {
109
+ const intelligence = {
110
+ listThreads: vi.fn(),
111
+ };
112
+ const runtime = createIntelligenceRuntime({
113
+ intelligence,
114
+ identifyUser: vi.fn().mockResolvedValue({ id: "user-1", name: "" }),
96
115
  });
97
116
 
98
117
  const response = await handleListThreads({
@@ -258,7 +277,50 @@ describe("thread handlers", () => {
258
277
  };
259
278
  const runtime = createIntelligenceRuntime({
260
279
  intelligence,
261
- identifyUser: vi.fn().mockResolvedValue({ id: "" }),
280
+ identifyUser: vi.fn().mockResolvedValue({ id: "", name: "User" }),
281
+ });
282
+
283
+ const updateResponse = await handleUpdateThread({
284
+ runtime,
285
+ request: createMutationRequest("/threads/thread-1", "PATCH", {
286
+ agentId: "agent-1",
287
+ }),
288
+ threadId: "thread-1",
289
+ });
290
+ expect(updateResponse.status).toBe(400);
291
+
292
+ const archiveResponse = await handleArchiveThread({
293
+ runtime,
294
+ request: createMutationRequest("/threads/thread-1/archive", "POST", {
295
+ agentId: "agent-1",
296
+ }),
297
+ threadId: "thread-1",
298
+ });
299
+ expect(archiveResponse.status).toBe(400);
300
+
301
+ const deleteResponse = await handleDeleteThread({
302
+ runtime,
303
+ request: createMutationRequest("/threads/thread-1", "DELETE", {
304
+ agentId: "agent-1",
305
+ }),
306
+ threadId: "thread-1",
307
+ });
308
+ expect(deleteResponse.status).toBe(400);
309
+
310
+ expect(intelligence.updateThread).not.toHaveBeenCalled();
311
+ expect(intelligence.archiveThread).not.toHaveBeenCalled();
312
+ expect(intelligence.deleteThread).not.toHaveBeenCalled();
313
+ });
314
+
315
+ it("returns 400 when identifyUser returns an invalid name for thread mutations", async () => {
316
+ const intelligence = {
317
+ updateThread: vi.fn(),
318
+ archiveThread: vi.fn(),
319
+ deleteThread: vi.fn(),
320
+ };
321
+ const runtime = createIntelligenceRuntime({
322
+ intelligence,
323
+ identifyUser: vi.fn().mockResolvedValue({ id: "user-1", name: "" }),
262
324
  });
263
325
 
264
326
  const updateResponse = await handleUpdateThread({
@@ -10,6 +10,10 @@
10
10
 
11
11
  import { multiEndpointSuite } from "./suites/multi-endpoint.suite";
12
12
  import { singleEndpointSuite } from "./suites/single-endpoint.suite";
13
+ import {
14
+ debugEventsSuite,
15
+ debugEventsProductionGuardSuite,
16
+ } from "./suites/debug-events.suite";
13
17
 
14
18
  // Server factories
15
19
  import { createExpressMultiServer } from "./servers/express-multi";
@@ -37,3 +41,18 @@ singleEndpointSuite("Node", createNodeSingleServer);
37
41
  singleEndpointSuite("Fetch", (opts) =>
38
42
  Promise.resolve(createFetchDirectHandler("single-route", opts)),
39
43
  );
44
+
45
+ // ─── Debug Events ───────────────────────────────────────────────────
46
+
47
+ debugEventsSuite("Express", createExpressMultiServer);
48
+ debugEventsSuite("Hono", createHonoMultiServer);
49
+ debugEventsSuite("Node", createNodeMultiServer);
50
+ debugEventsSuite("Fetch", (opts) =>
51
+ Promise.resolve(createFetchDirectHandler("multi-route", opts)),
52
+ );
53
+
54
+ debugEventsProductionGuardSuite(
55
+ () => createFetchDirectHandler("multi-route"),
56
+ "http://localhost",
57
+ "/api/copilotkit",
58
+ );