@botcord/daemon 0.2.72 → 0.2.73
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/dist/gateway/channels/botcord.js +65 -2
- package/dist/gateway/runtimes/codex.js +1 -1
- package/package.json +1 -1
- package/src/gateway/__tests__/botcord-channel.test.ts +88 -1
- package/src/gateway/__tests__/codex-adapter.test.ts +3 -1
- package/src/gateway/channels/botcord.ts +61 -2
- package/src/gateway/runtimes/codex.ts +1 -1
|
@@ -836,7 +836,7 @@ function normalizeBlockForHub(block, seq) {
|
|
|
836
836
|
}
|
|
837
837
|
if (kind === "tool_use") {
|
|
838
838
|
// Claude Code: assistant message w/ content[].type === "tool_use" → {id,name,input}
|
|
839
|
-
// Codex: item.started
|
|
839
|
+
// Codex: item.started for command_execution, file_change, mcp_tool_call, web_search
|
|
840
840
|
const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
|
|
841
841
|
const tu = contents.find((c) => c?.type === "tool_use");
|
|
842
842
|
if (tu) {
|
|
@@ -848,12 +848,19 @@ function normalizeBlockForHub(block, seq) {
|
|
|
848
848
|
}
|
|
849
849
|
else if (raw?.item && typeof raw.item === "object") {
|
|
850
850
|
payload.name = typeof raw.item.type === "string" ? raw.item.type : "tool";
|
|
851
|
-
|
|
851
|
+
const params = codexToolParams(raw.item);
|
|
852
|
+
if (Object.keys(params).length > 0)
|
|
853
|
+
payload.params = params;
|
|
854
|
+
if (typeof raw.item.id === "string")
|
|
855
|
+
payload.id = raw.item.id;
|
|
856
|
+
if (typeof raw.item.status === "string")
|
|
857
|
+
payload.status = raw.item.status;
|
|
852
858
|
}
|
|
853
859
|
return { kind: "tool_call", seq, payload };
|
|
854
860
|
}
|
|
855
861
|
if (kind === "tool_result") {
|
|
856
862
|
// Claude Code: {type:"user", message:{content:[{type:"tool_result",tool_use_id,content}]}}
|
|
863
|
+
// Codex: item.completed for command_execution, file_change, mcp_tool_call, web_search
|
|
857
864
|
const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
|
|
858
865
|
const tr = contents.find((c) => c?.type === "tool_result");
|
|
859
866
|
if (tr) {
|
|
@@ -870,6 +877,14 @@ function normalizeBlockForHub(block, seq) {
|
|
|
870
877
|
if (typeof tr.tool_use_id === "string")
|
|
871
878
|
payload.tool_use_id = tr.tool_use_id;
|
|
872
879
|
}
|
|
880
|
+
else if (raw?.item && typeof raw.item === "object") {
|
|
881
|
+
payload.name = typeof raw.item.type === "string" ? raw.item.type : "tool";
|
|
882
|
+
if (typeof raw.item.id === "string")
|
|
883
|
+
payload.tool_use_id = raw.item.id;
|
|
884
|
+
const result = codexToolResult(raw.item);
|
|
885
|
+
if (result)
|
|
886
|
+
payload.result = result;
|
|
887
|
+
}
|
|
873
888
|
return { kind: "tool_result", seq, payload };
|
|
874
889
|
}
|
|
875
890
|
if (kind === "system") {
|
|
@@ -928,6 +943,54 @@ function formatBlockDetails(raw) {
|
|
|
928
943
|
return String(raw);
|
|
929
944
|
}
|
|
930
945
|
}
|
|
946
|
+
function codexToolParams(item) {
|
|
947
|
+
const params = {};
|
|
948
|
+
for (const key of [
|
|
949
|
+
"command",
|
|
950
|
+
"cmd",
|
|
951
|
+
"args",
|
|
952
|
+
"path",
|
|
953
|
+
"query",
|
|
954
|
+
"url",
|
|
955
|
+
"name",
|
|
956
|
+
"input",
|
|
957
|
+
"arguments",
|
|
958
|
+
"action",
|
|
959
|
+
"changes",
|
|
960
|
+
]) {
|
|
961
|
+
const value = item[key];
|
|
962
|
+
if (value !== undefined && value !== null && value !== "")
|
|
963
|
+
params[key] = value;
|
|
964
|
+
}
|
|
965
|
+
const action = item.action;
|
|
966
|
+
if (action && typeof action === "object") {
|
|
967
|
+
for (const key of ["query", "url", "command", "path"]) {
|
|
968
|
+
const value = action[key];
|
|
969
|
+
if (value !== undefined && value !== null && value !== "")
|
|
970
|
+
params[key] = value;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return params;
|
|
974
|
+
}
|
|
975
|
+
function codexToolResult(item) {
|
|
976
|
+
const parts = [];
|
|
977
|
+
const status = typeof item.status === "string" ? item.status : "";
|
|
978
|
+
const exitCode = item.exit_code ?? item.exitCode;
|
|
979
|
+
if (status)
|
|
980
|
+
parts.push(`status: ${status}`);
|
|
981
|
+
if (typeof exitCode === "number" || typeof exitCode === "string")
|
|
982
|
+
parts.push(`exit_code: ${exitCode}`);
|
|
983
|
+
for (const key of ["output", "stdout", "stderr", "aggregated_output", "result", "summary"]) {
|
|
984
|
+
const value = item[key];
|
|
985
|
+
if (typeof value === "string" && value.trim())
|
|
986
|
+
parts.push(value.trim());
|
|
987
|
+
}
|
|
988
|
+
const results = item.results;
|
|
989
|
+
if (Array.isArray(results) && results.length > 0) {
|
|
990
|
+
parts.push(JSON.stringify(results, null, 2));
|
|
991
|
+
}
|
|
992
|
+
return parts.join("\n");
|
|
993
|
+
}
|
|
931
994
|
function extractContentText(content) {
|
|
932
995
|
if (!content)
|
|
933
996
|
return "";
|
|
@@ -380,7 +380,7 @@ function normalizeBlock(obj, seq) {
|
|
|
380
380
|
itemType === "file_change" ||
|
|
381
381
|
itemType === "mcp_tool_call" ||
|
|
382
382
|
itemType === "web_search") {
|
|
383
|
-
kind = "tool_use";
|
|
383
|
+
kind = type === "item.completed" ? "tool_result" : "tool_use";
|
|
384
384
|
}
|
|
385
385
|
}
|
|
386
386
|
return { raw: obj, kind, seq };
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { WebSocketServer, type WebSocket as WsType } from "ws";
|
|
3
3
|
import type { AddressInfo } from "node:net";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
createBotCordChannel,
|
|
6
|
+
__normalizeBlockForHubForTests,
|
|
7
|
+
type BotCordChannelClient,
|
|
8
|
+
} from "../channels/botcord.js";
|
|
5
9
|
import type { ChannelStartContext, GatewayInboundEnvelope } from "../types.js";
|
|
6
10
|
import type { GatewayLogger } from "../log.js";
|
|
7
11
|
import type { InboxMessage } from "@botcord/protocol-core";
|
|
@@ -649,6 +653,89 @@ describe("createBotCordChannel — ack + dedup", () => {
|
|
|
649
653
|
// ---------------------------------------------------------------------------
|
|
650
654
|
|
|
651
655
|
describe("createBotCordChannel — streamBlock()", () => {
|
|
656
|
+
it("normalizes Codex tool items without using internal ids as params", () => {
|
|
657
|
+
expect(
|
|
658
|
+
__normalizeBlockForHubForTests(
|
|
659
|
+
{
|
|
660
|
+
kind: "tool_use",
|
|
661
|
+
seq: 1,
|
|
662
|
+
raw: {
|
|
663
|
+
type: "item.started",
|
|
664
|
+
item: { id: "item_26", type: "command_execution", command: "rg stream-block" },
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
1,
|
|
668
|
+
),
|
|
669
|
+
).toEqual({
|
|
670
|
+
kind: "tool_call",
|
|
671
|
+
seq: 1,
|
|
672
|
+
payload: {
|
|
673
|
+
name: "command_execution",
|
|
674
|
+
id: "item_26",
|
|
675
|
+
params: { command: "rg stream-block" },
|
|
676
|
+
},
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
expect(
|
|
680
|
+
__normalizeBlockForHubForTests(
|
|
681
|
+
{
|
|
682
|
+
kind: "tool_use",
|
|
683
|
+
seq: 2,
|
|
684
|
+
raw: {
|
|
685
|
+
type: "item.started",
|
|
686
|
+
item: {
|
|
687
|
+
id: "ws_abc",
|
|
688
|
+
type: "web_search",
|
|
689
|
+
action: { type: "search", query: "codex stream response" },
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
},
|
|
693
|
+
2,
|
|
694
|
+
),
|
|
695
|
+
).toEqual({
|
|
696
|
+
kind: "tool_call",
|
|
697
|
+
seq: 2,
|
|
698
|
+
payload: {
|
|
699
|
+
name: "web_search",
|
|
700
|
+
id: "ws_abc",
|
|
701
|
+
params: {
|
|
702
|
+
action: { type: "search", query: "codex stream response" },
|
|
703
|
+
query: "codex stream response",
|
|
704
|
+
},
|
|
705
|
+
},
|
|
706
|
+
});
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
it("normalizes Codex completed tool items as results", () => {
|
|
710
|
+
expect(
|
|
711
|
+
__normalizeBlockForHubForTests(
|
|
712
|
+
{
|
|
713
|
+
kind: "tool_result",
|
|
714
|
+
seq: 3,
|
|
715
|
+
raw: {
|
|
716
|
+
type: "item.completed",
|
|
717
|
+
item: {
|
|
718
|
+
id: "item_26",
|
|
719
|
+
type: "command_execution",
|
|
720
|
+
status: "completed",
|
|
721
|
+
exit_code: 0,
|
|
722
|
+
output: "found 3 matches",
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
3,
|
|
727
|
+
),
|
|
728
|
+
).toEqual({
|
|
729
|
+
kind: "tool_result",
|
|
730
|
+
seq: 3,
|
|
731
|
+
payload: {
|
|
732
|
+
name: "command_execution",
|
|
733
|
+
tool_use_id: "item_26",
|
|
734
|
+
result: "status: completed\nexit_code: 0\nfound 3 matches",
|
|
735
|
+
},
|
|
736
|
+
});
|
|
737
|
+
});
|
|
738
|
+
|
|
652
739
|
it("POSTs to /hub/stream-block with the right trace_id + block", async () => {
|
|
653
740
|
const fetchSpy = vi.fn().mockResolvedValue(new Response(null, { status: 204 }));
|
|
654
741
|
const realFetch = globalThis.fetch;
|
|
@@ -77,13 +77,14 @@ process.exit(0);
|
|
|
77
77
|
expect(res.error).toBeUndefined();
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
it("emits tool_use
|
|
80
|
+
it("emits tool_use/tool_result StreamBlocks for command_execution items", async () => {
|
|
81
81
|
const script = makeScript(
|
|
82
82
|
"toolblock.js",
|
|
83
83
|
`
|
|
84
84
|
const lines = [
|
|
85
85
|
{type:"thread.started", thread_id:"01234567-89ab-7def-8123-456789abcde0"},
|
|
86
86
|
{type:"item.started", item:{id:"i0", type:"command_execution", command:"ls"}},
|
|
87
|
+
{type:"item.completed", item:{id:"i0", type:"command_execution", status:"completed", output:"ok"}},
|
|
87
88
|
{type:"item.completed", item:{id:"i1", type:"agent_message", text:"done"}},
|
|
88
89
|
];
|
|
89
90
|
for (const l of lines) process.stdout.write(JSON.stringify(l) + "\\n");
|
|
@@ -103,6 +104,7 @@ for (const l of lines) process.stdout.write(JSON.stringify(l) + "\\n");
|
|
|
103
104
|
});
|
|
104
105
|
expect(res.text).toBe("done");
|
|
105
106
|
expect(seen).toContain("tool_use");
|
|
107
|
+
expect(seen).toContain("tool_result");
|
|
106
108
|
expect(seen).toContain("assistant_text");
|
|
107
109
|
expect(seen).toContain("system");
|
|
108
110
|
});
|
|
@@ -979,7 +979,7 @@ function normalizeBlockForHub(
|
|
|
979
979
|
|
|
980
980
|
if (kind === "tool_use") {
|
|
981
981
|
// Claude Code: assistant message w/ content[].type === "tool_use" → {id,name,input}
|
|
982
|
-
// Codex: item.started
|
|
982
|
+
// Codex: item.started for command_execution, file_change, mcp_tool_call, web_search
|
|
983
983
|
const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
|
|
984
984
|
const tu = contents.find((c: any) => c?.type === "tool_use");
|
|
985
985
|
if (tu) {
|
|
@@ -988,13 +988,17 @@ function normalizeBlockForHub(
|
|
|
988
988
|
if (typeof tu.id === "string") payload.id = tu.id;
|
|
989
989
|
} else if (raw?.item && typeof raw.item === "object") {
|
|
990
990
|
payload.name = typeof raw.item.type === "string" ? raw.item.type : "tool";
|
|
991
|
-
|
|
991
|
+
const params = codexToolParams(raw.item);
|
|
992
|
+
if (Object.keys(params).length > 0) payload.params = params;
|
|
993
|
+
if (typeof raw.item.id === "string") payload.id = raw.item.id;
|
|
994
|
+
if (typeof raw.item.status === "string") payload.status = raw.item.status;
|
|
992
995
|
}
|
|
993
996
|
return { kind: "tool_call", seq, payload };
|
|
994
997
|
}
|
|
995
998
|
|
|
996
999
|
if (kind === "tool_result") {
|
|
997
1000
|
// Claude Code: {type:"user", message:{content:[{type:"tool_result",tool_use_id,content}]}}
|
|
1001
|
+
// Codex: item.completed for command_execution, file_change, mcp_tool_call, web_search
|
|
998
1002
|
const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
|
|
999
1003
|
const tr = contents.find((c: any) => c?.type === "tool_result");
|
|
1000
1004
|
if (tr) {
|
|
@@ -1008,6 +1012,11 @@ function normalizeBlockForHub(
|
|
|
1008
1012
|
}
|
|
1009
1013
|
payload.result = resultStr;
|
|
1010
1014
|
if (typeof tr.tool_use_id === "string") payload.tool_use_id = tr.tool_use_id;
|
|
1015
|
+
} else if (raw?.item && typeof raw.item === "object") {
|
|
1016
|
+
payload.name = typeof raw.item.type === "string" ? raw.item.type : "tool";
|
|
1017
|
+
if (typeof raw.item.id === "string") payload.tool_use_id = raw.item.id;
|
|
1018
|
+
const result = codexToolResult(raw.item);
|
|
1019
|
+
if (result) payload.result = result;
|
|
1011
1020
|
}
|
|
1012
1021
|
return { kind: "tool_result", seq, payload };
|
|
1013
1022
|
}
|
|
@@ -1062,6 +1071,56 @@ function formatBlockDetails(raw: unknown): string {
|
|
|
1062
1071
|
}
|
|
1063
1072
|
}
|
|
1064
1073
|
|
|
1074
|
+
function codexToolParams(item: Record<string, unknown>): Record<string, unknown> {
|
|
1075
|
+
const params: Record<string, unknown> = {};
|
|
1076
|
+
for (const key of [
|
|
1077
|
+
"command",
|
|
1078
|
+
"cmd",
|
|
1079
|
+
"args",
|
|
1080
|
+
"path",
|
|
1081
|
+
"query",
|
|
1082
|
+
"url",
|
|
1083
|
+
"name",
|
|
1084
|
+
"input",
|
|
1085
|
+
"arguments",
|
|
1086
|
+
"action",
|
|
1087
|
+
"changes",
|
|
1088
|
+
]) {
|
|
1089
|
+
const value = item[key];
|
|
1090
|
+
if (value !== undefined && value !== null && value !== "") params[key] = value;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const action = item.action as Record<string, unknown> | undefined;
|
|
1094
|
+
if (action && typeof action === "object") {
|
|
1095
|
+
for (const key of ["query", "url", "command", "path"]) {
|
|
1096
|
+
const value = action[key];
|
|
1097
|
+
if (value !== undefined && value !== null && value !== "") params[key] = value;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
return params;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
function codexToolResult(item: Record<string, unknown>): string {
|
|
1105
|
+
const parts: string[] = [];
|
|
1106
|
+
const status = typeof item.status === "string" ? item.status : "";
|
|
1107
|
+
const exitCode = item.exit_code ?? item.exitCode;
|
|
1108
|
+
if (status) parts.push(`status: ${status}`);
|
|
1109
|
+
if (typeof exitCode === "number" || typeof exitCode === "string") parts.push(`exit_code: ${exitCode}`);
|
|
1110
|
+
|
|
1111
|
+
for (const key of ["output", "stdout", "stderr", "aggregated_output", "result", "summary"]) {
|
|
1112
|
+
const value = item[key];
|
|
1113
|
+
if (typeof value === "string" && value.trim()) parts.push(value.trim());
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
const results = item.results;
|
|
1117
|
+
if (Array.isArray(results) && results.length > 0) {
|
|
1118
|
+
parts.push(JSON.stringify(results, null, 2));
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
return parts.join("\n");
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1065
1124
|
function extractContentText(content: unknown): string {
|
|
1066
1125
|
if (!content) return "";
|
|
1067
1126
|
if (typeof content === "string") return content;
|
|
@@ -420,7 +420,7 @@ function normalizeBlock(obj: any, seq: number): StreamBlock {
|
|
|
420
420
|
itemType === "mcp_tool_call" ||
|
|
421
421
|
itemType === "web_search"
|
|
422
422
|
) {
|
|
423
|
-
kind = "tool_use";
|
|
423
|
+
kind = type === "item.completed" ? "tool_result" : "tool_use";
|
|
424
424
|
}
|
|
425
425
|
}
|
|
426
426
|
return { raw: obj, kind, seq };
|