@copilotkit/runtime 1.55.0-next.7 → 1.55.0-next.9

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.
@@ -872,6 +872,30 @@ export class BuiltInAgent extends AbstractAgent {
872
872
  const abortController = new AbortController();
873
873
  this.abortController = abortController;
874
874
  let terminalEventEmitted = false;
875
+ let messageId = randomUUID();
876
+ let reasoningMessageId = randomUUID();
877
+ let isInReasoning = false;
878
+
879
+ // Auto-close an open reasoning lifecycle.
880
+ // Some AI SDK providers (notably @ai-sdk/anthropic) never emit "reasoning-end",
881
+ // which leaves downstream state machines stuck. This helper emits the
882
+ // missing REASONING_MESSAGE_END + REASONING_END events so the stream
883
+ // can transition to text, tool-call, or finish phases.
884
+ // Declared before try/catch so it is accessible in the catch block.
885
+ const closeReasoningIfOpen = () => {
886
+ if (!isInReasoning) return;
887
+ isInReasoning = false;
888
+ const reasoningMsgEnd: ReasoningMessageEndEvent = {
889
+ type: EventType.REASONING_MESSAGE_END,
890
+ messageId: reasoningMessageId,
891
+ };
892
+ subscriber.next(reasoningMsgEnd);
893
+ const reasoningEnd: ReasoningEndEvent = {
894
+ type: EventType.REASONING_END,
895
+ messageId: reasoningMessageId,
896
+ };
897
+ subscriber.next(reasoningEnd);
898
+ };
875
899
 
876
900
  try {
877
901
  // Add AG-UI state update tools
@@ -965,9 +989,6 @@ export class BuiltInAgent extends AbstractAgent {
965
989
  abortSignal: abortController.signal,
966
990
  });
967
991
 
968
- let messageId = randomUUID();
969
- let reasoningMessageId = randomUUID();
970
-
971
992
  const toolCallStates = new Map<
972
993
  string,
973
994
  {
@@ -989,6 +1010,12 @@ export class BuiltInAgent extends AbstractAgent {
989
1010
 
990
1011
  // Process fullStream events
991
1012
  for await (const part of response.fullStream) {
1013
+ // Close any open reasoning lifecycle on every event except
1014
+ // reasoning-delta, which arrives mid-block and must not interrupt it.
1015
+ if (part.type !== "reasoning-delta") {
1016
+ closeReasoningIfOpen();
1017
+ }
1018
+
992
1019
  switch (part.type) {
993
1020
  case "abort": {
994
1021
  const abortEndEvent: RunFinishedEvent = {
@@ -1004,12 +1031,13 @@ export class BuiltInAgent extends AbstractAgent {
1004
1031
  break;
1005
1032
  }
1006
1033
  case "reasoning-start": {
1007
- // New text message starting - use the SDK-provided id
1008
- // Use randomUUID() if part.id is falsy or "0" to prevent message merging issues
1034
+ // Use SDK-provided id, or generate a fresh UUID if id is falsy/"0"
1035
+ // to prevent consecutive reasoning blocks from sharing a messageId
1009
1036
  const providedId = "id" in part ? part.id : undefined;
1010
- if (providedId && providedId !== "0") {
1011
- reasoningMessageId = providedId as typeof reasoningMessageId;
1012
- }
1037
+ reasoningMessageId =
1038
+ providedId && providedId !== "0"
1039
+ ? (providedId as typeof reasoningMessageId)
1040
+ : randomUUID();
1013
1041
  const reasoningStartEvent: ReasoningStartEvent = {
1014
1042
  type: EventType.REASONING_START,
1015
1043
  messageId: reasoningMessageId,
@@ -1021,29 +1049,23 @@ export class BuiltInAgent extends AbstractAgent {
1021
1049
  role: "reasoning",
1022
1050
  };
1023
1051
  subscriber.next(reasoningMessageStart);
1052
+ isInReasoning = true;
1024
1053
  break;
1025
1054
  }
1026
1055
  case "reasoning-delta": {
1056
+ const delta = part.text ?? "";
1057
+ if (!delta) break; // skip — @ag-ui/core schema requires delta to be non-empty
1027
1058
  const reasoningDeltaEvent: ReasoningMessageContentEvent = {
1028
1059
  type: EventType.REASONING_MESSAGE_CONTENT,
1029
1060
  messageId: reasoningMessageId,
1030
- delta:
1031
- ("text" in part ? part.text : (part as any).delta) ?? "",
1061
+ delta,
1032
1062
  };
1033
1063
  subscriber.next(reasoningDeltaEvent);
1034
1064
  break;
1035
1065
  }
1036
1066
  case "reasoning-end": {
1037
- const reasoningMessageEnd: ReasoningMessageEndEvent = {
1038
- type: EventType.REASONING_MESSAGE_END,
1039
- messageId: reasoningMessageId,
1040
- };
1041
- subscriber.next(reasoningMessageEnd);
1042
- const reasoningEndEvent: ReasoningEndEvent = {
1043
- type: EventType.REASONING_END,
1044
- messageId: reasoningMessageId,
1045
- };
1046
- subscriber.next(reasoningEndEvent);
1067
+ // closeReasoningIfOpen() already called before the switch — no-op here
1068
+ // if the SDK never emits this event (e.g. @ai-sdk/anthropic).
1047
1069
  break;
1048
1070
  }
1049
1071
  case "tool-input-start": {
@@ -1236,6 +1258,7 @@ export class BuiltInAgent extends AbstractAgent {
1236
1258
  }
1237
1259
 
1238
1260
  if (!terminalEventEmitted) {
1261
+ closeReasoningIfOpen();
1239
1262
  if (abortController.signal.aborted) {
1240
1263
  // Let the runner finalize the stream on stop requests so it can
1241
1264
  // inject consistent closing events and a RUN_FINISHED marker.
@@ -1252,6 +1275,7 @@ export class BuiltInAgent extends AbstractAgent {
1252
1275
  subscriber.complete();
1253
1276
  }
1254
1277
  } catch (error) {
1278
+ closeReasoningIfOpen();
1255
1279
  if (abortController.signal.aborted) {
1256
1280
  subscriber.complete();
1257
1281
  } else {
@@ -48,15 +48,15 @@ describe("CopilotEndpointExpress middleware", () => {
48
48
  path: "/info",
49
49
  });
50
50
 
51
- await new Promise((resolve) => setTimeout(resolve, 20));
52
-
53
- expect(after).toHaveBeenCalledWith(
54
- expect.objectContaining({
55
- runtime,
56
- response: expect.any(Response),
57
- path: "/info",
58
- }),
59
- );
51
+ await vi.waitFor(() => {
52
+ expect(after).toHaveBeenCalledWith(
53
+ expect.objectContaining({
54
+ runtime,
55
+ response: expect.any(Response),
56
+ path: "/info",
57
+ }),
58
+ );
59
+ });
60
60
 
61
61
  expect(response.status).toBe(200);
62
62
  expect(response.body).toHaveProperty("version");
@@ -142,8 +142,9 @@ describe("CopilotEndpointExpress middleware", () => {
142
142
 
143
143
  expect(response.status).toBe(500);
144
144
  expect(logSpy).toHaveBeenCalled();
145
- await new Promise((resolve) => setTimeout(resolve, 50));
146
- expect(after).toHaveBeenCalled();
145
+ await vi.waitFor(() => {
146
+ expect(after).toHaveBeenCalled();
147
+ });
147
148
  });
148
149
 
149
150
  it("passes parsed messages to afterRequestMiddleware", async () => {
@@ -159,10 +160,11 @@ describe("CopilotEndpointExpress middleware", () => {
159
160
  const app = buildApp(runtime);
160
161
  const response = await request(app).get("/info");
161
162
 
162
- await new Promise((resolve) => setTimeout(resolve, 50));
163
+ await vi.waitFor(() => {
164
+ expect(after).toHaveBeenCalled();
165
+ });
163
166
 
164
167
  expect(response.status).toBe(200);
165
- expect(after).toHaveBeenCalled();
166
168
  expect(receivedParams).toHaveProperty("messages");
167
169
  expect(receivedParams.messages).toEqual([]);
168
170
  });
@@ -184,15 +186,15 @@ describe("CopilotEndpointExpress middleware", () => {
184
186
 
185
187
  expect(response.status).toBe(200);
186
188
 
187
- await new Promise((resolve) => setTimeout(resolve, 20));
188
-
189
- expect(after).toHaveBeenCalledWith(
190
- expect.objectContaining({
191
- runtime,
192
- response: expect.any(Response),
193
- path: "/info",
194
- }),
195
- );
189
+ await vi.waitFor(() => {
190
+ expect(after).toHaveBeenCalledWith(
191
+ expect.objectContaining({
192
+ runtime,
193
+ response: expect.any(Response),
194
+ path: "/info",
195
+ }),
196
+ );
197
+ });
196
198
 
197
199
  expect(logSpy).toHaveBeenCalledWith(
198
200
  expect.objectContaining({
@@ -52,15 +52,15 @@ describe("CopilotEndpointSingleRouteExpress middleware", () => {
52
52
  path: "/rpc",
53
53
  });
54
54
 
55
- await new Promise((resolve) => setTimeout(resolve, 20));
56
-
57
- expect(after).toHaveBeenCalledWith(
58
- expect.objectContaining({
59
- runtime,
60
- response: expect.any(Response),
61
- path: "/rpc",
62
- }),
63
- );
55
+ await vi.waitFor(() => {
56
+ expect(after).toHaveBeenCalledWith(
57
+ expect.objectContaining({
58
+ runtime,
59
+ response: expect.any(Response),
60
+ path: "/rpc",
61
+ }),
62
+ );
63
+ });
64
64
 
65
65
  expect(response.status).toBe(200);
66
66
  expect(response.body).toHaveProperty("version");
@@ -147,8 +147,9 @@ describe("CopilotEndpointSingleRouteExpress middleware", () => {
147
147
 
148
148
  expect(response.status).toBe(500);
149
149
  expect(logSpy).toHaveBeenCalled();
150
- await new Promise((resolve) => setTimeout(resolve, 50));
151
- expect(after).toHaveBeenCalled();
150
+ await vi.waitFor(() => {
151
+ expect(after).toHaveBeenCalled();
152
+ });
152
153
  });
153
154
 
154
155
  it("passes parsed messages to afterRequestMiddleware", async () => {
@@ -164,10 +165,11 @@ describe("CopilotEndpointSingleRouteExpress middleware", () => {
164
165
  const app = buildApp(runtime);
165
166
  const response = await rpcRequest(app, { method: "info" });
166
167
 
167
- await new Promise((resolve) => setTimeout(resolve, 50));
168
+ await vi.waitFor(() => {
169
+ expect(after).toHaveBeenCalled();
170
+ });
168
171
 
169
172
  expect(response.status).toBe(200);
170
- expect(after).toHaveBeenCalled();
171
173
  expect(receivedParams).toHaveProperty("messages");
172
174
  expect(receivedParams.messages).toEqual([]);
173
175
  });
@@ -189,15 +191,15 @@ describe("CopilotEndpointSingleRouteExpress middleware", () => {
189
191
 
190
192
  expect(response.status).toBe(200);
191
193
 
192
- await new Promise((resolve) => setTimeout(resolve, 20));
193
-
194
- expect(after).toHaveBeenCalledWith(
195
- expect.objectContaining({
196
- runtime,
197
- response: expect.any(Response),
198
- path: "/rpc",
199
- }),
200
- );
194
+ await vi.waitFor(() => {
195
+ expect(after).toHaveBeenCalledWith(
196
+ expect.objectContaining({
197
+ runtime,
198
+ response: expect.any(Response),
199
+ path: "/rpc",
200
+ }),
201
+ );
202
+ });
201
203
 
202
204
  expect(logSpy).toHaveBeenCalledWith(
203
205
  expect.objectContaining({