postgresai 0.14.0-dev.70 → 0.14.0-dev.72

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.
@@ -321,7 +321,7 @@ describe("MCP Server", () => {
321
321
  );
322
322
  });
323
323
 
324
- const response = await handleToolCall(createRequest("view_issue", { issue_id: "issue-1" }));
324
+ const response = await handleToolCall(createRequest("view_issue", { issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" }));
325
325
 
326
326
  expect(response.isError).toBeUndefined();
327
327
  const parsed = JSON.parse(getResponseText(response));
@@ -360,7 +360,7 @@ describe("MCP Server", () => {
360
360
  });
361
361
 
362
362
  const response = await handleToolCall(
363
- createRequest("post_issue_comment", { issue_id: "issue-1", content: "" })
363
+ createRequest("post_issue_comment", { issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", content: "" })
364
364
  );
365
365
 
366
366
  expect(response.isError).toBe(true);
@@ -390,7 +390,7 @@ describe("MCP Server", () => {
390
390
 
391
391
  await handleToolCall(
392
392
  createRequest("post_issue_comment", {
393
- issue_id: "issue-1",
393
+ issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
394
394
  content: "line1\\nline2\\ttab",
395
395
  })
396
396
  );
@@ -423,7 +423,7 @@ describe("MCP Server", () => {
423
423
 
424
424
  const response = await handleToolCall(
425
425
  createRequest("post_issue_comment", {
426
- issue_id: "issue-1",
426
+ issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
427
427
  content: "Reply content",
428
428
  parent_comment_id: "parent-1",
429
429
  })
@@ -618,7 +618,7 @@ describe("MCP Server", () => {
618
618
  defaultProject: null,
619
619
  });
620
620
 
621
- const response = await handleToolCall(createRequest("update_issue", { issue_id: "issue-1" }));
621
+ const response = await handleToolCall(createRequest("update_issue", { issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" }));
622
622
 
623
623
  expect(response.isError).toBe(true);
624
624
  expect(getResponseText(response)).toContain("At least one field to update is required");
@@ -635,7 +635,7 @@ describe("MCP Server", () => {
635
635
  });
636
636
 
637
637
  const response = await handleToolCall(
638
- createRequest("update_issue", { issue_id: "issue-1", status: 2 })
638
+ createRequest("update_issue", { issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", status: 2 })
639
639
  );
640
640
 
641
641
  expect(response.isError).toBe(true);
@@ -653,7 +653,7 @@ describe("MCP Server", () => {
653
653
  });
654
654
 
655
655
  const response = await handleToolCall(
656
- createRequest("update_issue", { issue_id: "issue-1", status: -1 })
656
+ createRequest("update_issue", { issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", status: -1 })
657
657
  );
658
658
 
659
659
  expect(response.isError).toBe(true);
@@ -683,7 +683,7 @@ describe("MCP Server", () => {
683
683
 
684
684
  await handleToolCall(
685
685
  createRequest("update_issue", {
686
- issue_id: "issue-1",
686
+ issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
687
687
  title: "Updated\\nTitle",
688
688
  description: "Updated\\tDescription",
689
689
  })
@@ -715,7 +715,7 @@ describe("MCP Server", () => {
715
715
  );
716
716
 
717
717
  const response = await handleToolCall(
718
- createRequest("update_issue", { issue_id: "issue-1", title: "New Title" })
718
+ createRequest("update_issue", { issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", title: "New Title" })
719
719
  );
720
720
 
721
721
  expect(response.isError).toBeUndefined();
@@ -743,7 +743,7 @@ describe("MCP Server", () => {
743
743
  });
744
744
 
745
745
  const response = await handleToolCall(
746
- createRequest("update_issue", { issue_id: "issue-1", status: 1 })
746
+ createRequest("update_issue", { issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", status: 1 })
747
747
  );
748
748
 
749
749
  expect(response.isError).toBeUndefined();
@@ -774,7 +774,7 @@ describe("MCP Server", () => {
774
774
  });
775
775
 
776
776
  const response = await handleToolCall(
777
- createRequest("update_issue", { issue_id: "issue-1", labels: ["new-label"] })
777
+ createRequest("update_issue", { issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", labels: ["new-label"] })
778
778
  );
779
779
 
780
780
  expect(response.isError).toBeUndefined();
@@ -805,7 +805,7 @@ describe("MCP Server", () => {
805
805
  });
806
806
 
807
807
  const response = await handleToolCall(
808
- createRequest("update_issue", { issue_id: "issue-1", status: 0 })
808
+ createRequest("update_issue", { issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", status: 0 })
809
809
  );
810
810
 
811
811
  expect(response.isError).toBeUndefined();
@@ -919,6 +919,545 @@ describe("MCP Server", () => {
919
919
  });
920
920
  });
921
921
 
922
+ describe("view_action_item tool", () => {
923
+ test("returns error when no IDs provided", async () => {
924
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
925
+ apiKey: "test-key",
926
+ baseUrl: null,
927
+ orgId: null,
928
+ defaultProject: null,
929
+ });
930
+
931
+ const response = await handleToolCall(createRequest("view_action_item", {}));
932
+
933
+ expect(response.isError).toBe(true);
934
+ expect(getResponseText(response)).toBe("action_item_id or action_item_ids is required");
935
+
936
+ readConfigSpy.mockRestore();
937
+ });
938
+
939
+ test("returns error when action_item_id is empty", async () => {
940
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
941
+ apiKey: "test-key",
942
+ baseUrl: null,
943
+ orgId: null,
944
+ defaultProject: null,
945
+ });
946
+
947
+ const response = await handleToolCall(createRequest("view_action_item", { action_item_id: "" }));
948
+
949
+ expect(response.isError).toBe(true);
950
+ expect(getResponseText(response)).toBe("action_item_id or action_item_ids is required");
951
+
952
+ readConfigSpy.mockRestore();
953
+ });
954
+
955
+ test("returns error when action_item_ids is empty array", async () => {
956
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
957
+ apiKey: "test-key",
958
+ baseUrl: null,
959
+ orgId: null,
960
+ defaultProject: null,
961
+ });
962
+
963
+ const response = await handleToolCall(createRequest("view_action_item", { action_item_ids: [] }));
964
+
965
+ expect(response.isError).toBe(true);
966
+ expect(getResponseText(response)).toBe("action_item_id or action_item_ids is required");
967
+
968
+ readConfigSpy.mockRestore();
969
+ });
970
+
971
+ test("returns error when action_item_id is not a valid UUID", async () => {
972
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
973
+ apiKey: "test-key",
974
+ baseUrl: null,
975
+ orgId: null,
976
+ defaultProject: null,
977
+ });
978
+
979
+ const response = await handleToolCall(createRequest("view_action_item", { action_item_id: "invalid-id-format" }));
980
+
981
+ expect(response.isError).toBe(true);
982
+ expect(getResponseText(response)).toBe("actionItemId is required and must be a valid UUID");
983
+
984
+ readConfigSpy.mockRestore();
985
+ });
986
+
987
+ test("returns error when action item not found", async () => {
988
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
989
+ apiKey: "test-key",
990
+ baseUrl: null,
991
+ orgId: null,
992
+ defaultProject: null,
993
+ });
994
+
995
+ globalThis.fetch = mock(() =>
996
+ Promise.resolve(
997
+ new Response("[]", {
998
+ status: 200,
999
+ headers: { "Content-Type": "application/json" },
1000
+ })
1001
+ )
1002
+ );
1003
+
1004
+ const response = await handleToolCall(createRequest("view_action_item", { action_item_id: "00000000-0000-0000-0000-000000000000" }));
1005
+
1006
+ expect(response.isError).toBe(true);
1007
+ expect(getResponseText(response)).toBe("Action item(s) not found");
1008
+
1009
+ readConfigSpy.mockRestore();
1010
+ });
1011
+
1012
+ test("successfully returns single action item details", async () => {
1013
+ const mockActionItem = {
1014
+ id: "11111111-1111-1111-1111-111111111111",
1015
+ issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
1016
+ title: "Fix index",
1017
+ description: "Drop unused index",
1018
+ severity: 3,
1019
+ is_done: false,
1020
+ status: "waiting_for_approval",
1021
+ sql_action: "DROP INDEX CONCURRENTLY idx_unused;",
1022
+ configs: [{ parameter: "work_mem", value: "256MB" }],
1023
+ };
1024
+
1025
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
1026
+ apiKey: "test-key",
1027
+ baseUrl: null,
1028
+ orgId: null,
1029
+ defaultProject: null,
1030
+ });
1031
+
1032
+ globalThis.fetch = mock(() =>
1033
+ Promise.resolve(
1034
+ new Response(JSON.stringify([mockActionItem]), {
1035
+ status: 200,
1036
+ headers: { "Content-Type": "application/json" },
1037
+ })
1038
+ )
1039
+ );
1040
+
1041
+ const response = await handleToolCall(createRequest("view_action_item", { action_item_id: "11111111-1111-1111-1111-111111111111" }));
1042
+
1043
+ expect(response.isError).toBeUndefined();
1044
+ const parsed = JSON.parse(getResponseText(response));
1045
+ expect(Array.isArray(parsed)).toBe(true);
1046
+ expect(parsed[0].title).toBe("Fix index");
1047
+ expect(parsed[0].sql_action).toBe("DROP INDEX CONCURRENTLY idx_unused;");
1048
+ expect(parsed[0].configs).toEqual([{ parameter: "work_mem", value: "256MB" }]);
1049
+
1050
+ readConfigSpy.mockRestore();
1051
+ });
1052
+
1053
+ test("successfully returns multiple action items", async () => {
1054
+ const mockActionItems = [
1055
+ { id: "11111111-1111-1111-1111-111111111111", title: "Fix index", severity: 3 },
1056
+ { id: "22222222-2222-2222-2222-222222222222", title: "Update config", severity: 2 },
1057
+ ];
1058
+
1059
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
1060
+ apiKey: "test-key",
1061
+ baseUrl: null,
1062
+ orgId: null,
1063
+ defaultProject: null,
1064
+ });
1065
+
1066
+ let capturedUrl: string | undefined;
1067
+ globalThis.fetch = mock((url: string) => {
1068
+ capturedUrl = url;
1069
+ return Promise.resolve(
1070
+ new Response(JSON.stringify(mockActionItems), {
1071
+ status: 200,
1072
+ headers: { "Content-Type": "application/json" },
1073
+ })
1074
+ );
1075
+ });
1076
+
1077
+ const response = await handleToolCall(createRequest("view_action_item", { action_item_ids: ["11111111-1111-1111-1111-111111111111", "22222222-2222-2222-2222-222222222222"] }));
1078
+
1079
+ expect(response.isError).toBeUndefined();
1080
+ const parsed = JSON.parse(getResponseText(response));
1081
+ expect(parsed).toHaveLength(2);
1082
+ expect(parsed[0].title).toBe("Fix index");
1083
+ expect(parsed[1].title).toBe("Update config");
1084
+ // Verify the URL uses in.() syntax
1085
+ expect(capturedUrl).toContain("id=in.");
1086
+
1087
+ readConfigSpy.mockRestore();
1088
+ });
1089
+ });
1090
+
1091
+ describe("list_action_items tool", () => {
1092
+ test("returns error when issue_id is empty", async () => {
1093
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
1094
+ apiKey: "test-key",
1095
+ baseUrl: null,
1096
+ orgId: null,
1097
+ defaultProject: null,
1098
+ });
1099
+
1100
+ const response = await handleToolCall(createRequest("list_action_items", { issue_id: "" }));
1101
+
1102
+ expect(response.isError).toBe(true);
1103
+ expect(getResponseText(response)).toBe("issue_id is required");
1104
+
1105
+ readConfigSpy.mockRestore();
1106
+ });
1107
+
1108
+ test("returns error when issue_id is whitespace only", async () => {
1109
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
1110
+ apiKey: "test-key",
1111
+ baseUrl: null,
1112
+ orgId: null,
1113
+ defaultProject: null,
1114
+ });
1115
+
1116
+ const response = await handleToolCall(createRequest("list_action_items", { issue_id: " " }));
1117
+
1118
+ expect(response.isError).toBe(true);
1119
+ expect(getResponseText(response)).toBe("issue_id is required");
1120
+
1121
+ readConfigSpy.mockRestore();
1122
+ });
1123
+
1124
+ test("successfully returns action items list as JSON", async () => {
1125
+ const mockActionItems = [
1126
+ { id: "action-1", title: "First Action", severity: 1 },
1127
+ { id: "action-2", title: "Second Action", severity: 2 },
1128
+ ];
1129
+
1130
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
1131
+ apiKey: "test-key",
1132
+ baseUrl: null,
1133
+ orgId: null,
1134
+ defaultProject: null,
1135
+ });
1136
+
1137
+ globalThis.fetch = mock(() =>
1138
+ Promise.resolve(
1139
+ new Response(JSON.stringify(mockActionItems), {
1140
+ status: 200,
1141
+ headers: { "Content-Type": "application/json" },
1142
+ })
1143
+ )
1144
+ );
1145
+
1146
+ const response = await handleToolCall(createRequest("list_action_items", { issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" }));
1147
+
1148
+ expect(response.isError).toBeUndefined();
1149
+ const parsed = JSON.parse(getResponseText(response));
1150
+ expect(parsed).toHaveLength(2);
1151
+ expect(parsed[0].title).toBe("First Action");
1152
+
1153
+ readConfigSpy.mockRestore();
1154
+ });
1155
+ });
1156
+
1157
+ describe("create_action_item tool", () => {
1158
+ test("returns error when issue_id is empty", async () => {
1159
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
1160
+ apiKey: "test-key",
1161
+ baseUrl: null,
1162
+ orgId: null,
1163
+ defaultProject: null,
1164
+ });
1165
+
1166
+ const response = await handleToolCall(
1167
+ createRequest("create_action_item", { issue_id: "", title: "Test" })
1168
+ );
1169
+
1170
+ expect(response.isError).toBe(true);
1171
+ expect(getResponseText(response)).toBe("issue_id is required");
1172
+
1173
+ readConfigSpy.mockRestore();
1174
+ });
1175
+
1176
+ test("returns error when title is empty", async () => {
1177
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
1178
+ apiKey: "test-key",
1179
+ baseUrl: null,
1180
+ orgId: null,
1181
+ defaultProject: null,
1182
+ });
1183
+
1184
+ const response = await handleToolCall(
1185
+ createRequest("create_action_item", { issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", title: "" })
1186
+ );
1187
+
1188
+ expect(response.isError).toBe(true);
1189
+ expect(getResponseText(response)).toBe("title is required");
1190
+
1191
+ readConfigSpy.mockRestore();
1192
+ });
1193
+
1194
+ test("successfully creates action item with minimal params", async () => {
1195
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
1196
+ apiKey: "test-key",
1197
+ baseUrl: null,
1198
+ orgId: null,
1199
+ defaultProject: null,
1200
+ });
1201
+
1202
+ let capturedBody: string | undefined;
1203
+ globalThis.fetch = mock((url: string, options?: RequestInit) => {
1204
+ capturedBody = options?.body as string;
1205
+ return Promise.resolve(
1206
+ new Response(JSON.stringify("new-action-item-id"), {
1207
+ status: 200,
1208
+ headers: { "Content-Type": "application/json" },
1209
+ })
1210
+ );
1211
+ });
1212
+
1213
+ const response = await handleToolCall(
1214
+ createRequest("create_action_item", {
1215
+ issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
1216
+ title: "Fix the index",
1217
+ })
1218
+ );
1219
+
1220
+ expect(response.isError).toBeUndefined();
1221
+ expect(capturedBody).toBeDefined();
1222
+ const parsed = JSON.parse(capturedBody!);
1223
+ expect(parsed.issue_id).toBe("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
1224
+ expect(parsed.title).toBe("Fix the index");
1225
+
1226
+ readConfigSpy.mockRestore();
1227
+ });
1228
+
1229
+ test("successfully creates action item with all params", async () => {
1230
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
1231
+ apiKey: "test-key",
1232
+ baseUrl: null,
1233
+ orgId: null,
1234
+ defaultProject: null,
1235
+ });
1236
+
1237
+ let capturedBody: string | undefined;
1238
+ globalThis.fetch = mock((url: string, options?: RequestInit) => {
1239
+ capturedBody = options?.body as string;
1240
+ return Promise.resolve(
1241
+ new Response(JSON.stringify("new-action-item-id"), {
1242
+ status: 200,
1243
+ headers: { "Content-Type": "application/json" },
1244
+ })
1245
+ );
1246
+ });
1247
+
1248
+ const response = await handleToolCall(
1249
+ createRequest("create_action_item", {
1250
+ issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
1251
+ title: "Fix the index",
1252
+ description: "Drop the unused index to improve performance",
1253
+ sql_action: "DROP INDEX CONCURRENTLY idx_unused;",
1254
+ configs: [{ parameter: "work_mem", value: "256MB" }],
1255
+ })
1256
+ );
1257
+
1258
+ expect(response.isError).toBeUndefined();
1259
+ expect(capturedBody).toBeDefined();
1260
+ const parsed = JSON.parse(capturedBody!);
1261
+ expect(parsed.issue_id).toBe("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
1262
+ expect(parsed.title).toBe("Fix the index");
1263
+ expect(parsed.description).toBe("Drop the unused index to improve performance");
1264
+ expect(parsed.sql_action).toBe("DROP INDEX CONCURRENTLY idx_unused;");
1265
+ expect(parsed.configs).toEqual([{ parameter: "work_mem", value: "256MB" }]);
1266
+
1267
+ readConfigSpy.mockRestore();
1268
+ });
1269
+
1270
+ test("interprets escape sequences in title and description", async () => {
1271
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
1272
+ apiKey: "test-key",
1273
+ baseUrl: null,
1274
+ orgId: null,
1275
+ defaultProject: null,
1276
+ });
1277
+
1278
+ let capturedBody: string | undefined;
1279
+ globalThis.fetch = mock((url: string, options?: RequestInit) => {
1280
+ capturedBody = options?.body as string;
1281
+ return Promise.resolve(
1282
+ new Response(JSON.stringify("new-action-item-id"), {
1283
+ status: 200,
1284
+ headers: { "Content-Type": "application/json" },
1285
+ })
1286
+ );
1287
+ });
1288
+
1289
+ await handleToolCall(
1290
+ createRequest("create_action_item", {
1291
+ issue_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
1292
+ title: "Title\\nwith newline",
1293
+ description: "Desc\\twith tab",
1294
+ })
1295
+ );
1296
+
1297
+ expect(capturedBody).toBeDefined();
1298
+ const parsed = JSON.parse(capturedBody!);
1299
+ expect(parsed.title).toBe("Title\nwith newline");
1300
+ expect(parsed.description).toBe("Desc\twith tab");
1301
+
1302
+ readConfigSpy.mockRestore();
1303
+ });
1304
+ });
1305
+
1306
+ describe("update_action_item tool", () => {
1307
+ test("returns error when action_item_id is empty", async () => {
1308
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
1309
+ apiKey: "test-key",
1310
+ baseUrl: null,
1311
+ orgId: null,
1312
+ defaultProject: null,
1313
+ });
1314
+
1315
+ const response = await handleToolCall(
1316
+ createRequest("update_action_item", { action_item_id: "", title: "New Title" })
1317
+ );
1318
+
1319
+ expect(response.isError).toBe(true);
1320
+ expect(getResponseText(response)).toBe("action_item_id is required");
1321
+
1322
+ readConfigSpy.mockRestore();
1323
+ });
1324
+
1325
+ test("returns error when no update fields provided", async () => {
1326
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
1327
+ apiKey: "test-key",
1328
+ baseUrl: null,
1329
+ orgId: null,
1330
+ defaultProject: null,
1331
+ });
1332
+
1333
+ const response = await handleToolCall(
1334
+ createRequest("update_action_item", { action_item_id: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" })
1335
+ );
1336
+
1337
+ expect(response.isError).toBe(true);
1338
+ expect(getResponseText(response)).toContain("At least one field to update is required");
1339
+
1340
+ readConfigSpy.mockRestore();
1341
+ });
1342
+
1343
+ test("returns error when status is invalid", async () => {
1344
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
1345
+ apiKey: "test-key",
1346
+ baseUrl: null,
1347
+ orgId: null,
1348
+ defaultProject: null,
1349
+ });
1350
+
1351
+ const response = await handleToolCall(
1352
+ createRequest("update_action_item", { action_item_id: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", status: "invalid_status" })
1353
+ );
1354
+
1355
+ expect(response.isError).toBe(true);
1356
+ expect(getResponseText(response)).toContain("status must be");
1357
+
1358
+ readConfigSpy.mockRestore();
1359
+ });
1360
+
1361
+ test("successfully updates with only title", async () => {
1362
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
1363
+ apiKey: "test-key",
1364
+ baseUrl: null,
1365
+ orgId: null,
1366
+ defaultProject: null,
1367
+ });
1368
+
1369
+ let capturedBody: string | undefined;
1370
+ globalThis.fetch = mock((url: string, options?: RequestInit) => {
1371
+ capturedBody = options?.body as string;
1372
+ return Promise.resolve(
1373
+ new Response("", {
1374
+ status: 200,
1375
+ headers: { "Content-Type": "application/json" },
1376
+ })
1377
+ );
1378
+ });
1379
+
1380
+ const response = await handleToolCall(
1381
+ createRequest("update_action_item", { action_item_id: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", title: "New Title" })
1382
+ );
1383
+
1384
+ expect(response.isError).toBeUndefined();
1385
+ expect(capturedBody).toBeDefined();
1386
+ const parsed = JSON.parse(capturedBody!);
1387
+ expect(parsed.action_item_id).toBe("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb");
1388
+ expect(parsed.title).toBe("New Title");
1389
+
1390
+ readConfigSpy.mockRestore();
1391
+ });
1392
+
1393
+ test("successfully updates is_done", async () => {
1394
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
1395
+ apiKey: "test-key",
1396
+ baseUrl: null,
1397
+ orgId: null,
1398
+ defaultProject: null,
1399
+ });
1400
+
1401
+ let capturedBody: string | undefined;
1402
+ globalThis.fetch = mock((url: string, options?: RequestInit) => {
1403
+ capturedBody = options?.body as string;
1404
+ return Promise.resolve(
1405
+ new Response("", {
1406
+ status: 200,
1407
+ headers: { "Content-Type": "application/json" },
1408
+ })
1409
+ );
1410
+ });
1411
+
1412
+ const response = await handleToolCall(
1413
+ createRequest("update_action_item", { action_item_id: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", is_done: true })
1414
+ );
1415
+
1416
+ expect(response.isError).toBeUndefined();
1417
+ expect(capturedBody).toBeDefined();
1418
+ const parsed = JSON.parse(capturedBody!);
1419
+ expect(parsed.is_done).toBe(true);
1420
+
1421
+ readConfigSpy.mockRestore();
1422
+ });
1423
+
1424
+ test("successfully updates status with status_reason", async () => {
1425
+ const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({
1426
+ apiKey: "test-key",
1427
+ baseUrl: null,
1428
+ orgId: null,
1429
+ defaultProject: null,
1430
+ });
1431
+
1432
+ let capturedBody: string | undefined;
1433
+ globalThis.fetch = mock((url: string, options?: RequestInit) => {
1434
+ capturedBody = options?.body as string;
1435
+ return Promise.resolve(
1436
+ new Response("", {
1437
+ status: 200,
1438
+ headers: { "Content-Type": "application/json" },
1439
+ })
1440
+ );
1441
+ });
1442
+
1443
+ const response = await handleToolCall(
1444
+ createRequest("update_action_item", {
1445
+ action_item_id: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
1446
+ status: "approved",
1447
+ status_reason: "Looks good to me",
1448
+ })
1449
+ );
1450
+
1451
+ expect(response.isError).toBeUndefined();
1452
+ expect(capturedBody).toBeDefined();
1453
+ const parsed = JSON.parse(capturedBody!);
1454
+ expect(parsed.status).toBe("approved");
1455
+ expect(parsed.status_reason).toBe("Looks good to me");
1456
+
1457
+ readConfigSpy.mockRestore();
1458
+ });
1459
+ });
1460
+
922
1461
  describe("unknown tool handling", () => {
923
1462
  test("returns error for unknown tool name", async () => {
924
1463
  const readConfigSpy = spyOn(config, "readConfig").mockReturnValue({