@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.
- package/CHANGELOG.md +18 -0
- package/dist/agent/index.cjs +184 -173
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts.map +1 -1
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/agent/index.mjs +184 -173
- package/dist/agent/index.mjs.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/package.json +2 -2
- package/src/agent/__tests__/basic-agent.test.ts +455 -5
- package/src/agent/__tests__/test-helpers.ts +27 -12
- package/src/agent/index.ts +44 -20
- package/src/v2/runtime/__tests__/middleware-express.test.ts +24 -22
- package/src/v2/runtime/__tests__/middleware-single-express.test.ts +24 -22
package/src/agent/index.ts
CHANGED
|
@@ -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
|
-
//
|
|
1008
|
-
//
|
|
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
|
-
|
|
1011
|
-
|
|
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
|
-
|
|
1038
|
-
|
|
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
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
146
|
-
|
|
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
|
|
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
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
151
|
-
|
|
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
|
|
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
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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({
|