@botcord/daemon 0.2.76 → 0.2.77
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.
|
@@ -57,11 +57,12 @@ function defaultClientFactory(input) {
|
|
|
57
57
|
function isOwnerTrust(msg) {
|
|
58
58
|
if (msg.room_id?.startsWith(OWNER_CHAT_PREFIX))
|
|
59
59
|
return true;
|
|
60
|
-
|
|
60
|
+
const sourceType = msg.source_type;
|
|
61
|
+
if (sourceType === "dashboard_user_chat")
|
|
61
62
|
return true;
|
|
62
63
|
// Cloud Agent run tasks are Hub-issued on the user's behalf, same
|
|
63
64
|
// trust posture as owner chat.
|
|
64
|
-
if (
|
|
65
|
+
if (sourceType === "cloud_agent_run")
|
|
65
66
|
return true;
|
|
66
67
|
return false;
|
|
67
68
|
}
|
|
@@ -100,9 +101,10 @@ function normalizeInbox(msg, options) {
|
|
|
100
101
|
// run completes). All other envelope types (notification, system,
|
|
101
102
|
// contact_added/removed, …) are still filtered out — they belong in
|
|
102
103
|
// a separate push-notification path that daemon does not yet implement.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
const envType = env.type;
|
|
105
|
+
if (envType !== "message" &&
|
|
106
|
+
envType !== "contact_request" &&
|
|
107
|
+
envType !== "cloud_run")
|
|
106
108
|
return null;
|
|
107
109
|
if (!msg.room_id)
|
|
108
110
|
return null;
|
|
@@ -113,7 +115,8 @@ function normalizeInbox(msg, options) {
|
|
|
113
115
|
const text = ownerTrust ? rawText : sanitizeUntrustedContent(rawText);
|
|
114
116
|
const isDm = msg.room_id.startsWith(DM_ROOM_PREFIX);
|
|
115
117
|
const isOwnerChat = msg.room_id.startsWith(OWNER_CHAT_PREFIX);
|
|
116
|
-
const
|
|
118
|
+
const sourceType = msg.source_type;
|
|
119
|
+
const senderKind = ownerTrust || sourceType === "dashboard_human_room" ? "user" : "agent";
|
|
117
120
|
const senderName = msg.source_user_name ?? undefined;
|
|
118
121
|
const threadId = msg.topic_id ?? msg.topic ?? null;
|
|
119
122
|
const streamable = isOwnerChat;
|
|
@@ -860,55 +863,29 @@ function normalizeBlockForHub(block, seq) {
|
|
|
860
863
|
return { kind: "assistant", seq, payload: { text } };
|
|
861
864
|
}
|
|
862
865
|
if (kind === "tool_use") {
|
|
863
|
-
// Claude Code
|
|
864
|
-
//
|
|
865
|
-
|
|
866
|
-
const
|
|
867
|
-
if (
|
|
868
|
-
payload.name =
|
|
869
|
-
if (
|
|
870
|
-
payload.params =
|
|
871
|
-
if (
|
|
872
|
-
payload.id =
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
payload.name = typeof raw.item.type === "string" ? raw.item.type : "tool";
|
|
876
|
-
const params = codexToolParams(raw.item);
|
|
877
|
-
if (Object.keys(params).length > 0)
|
|
878
|
-
payload.params = params;
|
|
879
|
-
if (typeof raw.item.id === "string")
|
|
880
|
-
payload.id = raw.item.id;
|
|
881
|
-
if (typeof raw.item.status === "string")
|
|
882
|
-
payload.status = raw.item.status;
|
|
866
|
+
// Claude Code, Codex, DeepSeek TUI, Kimi, and ACP all expose tool calls
|
|
867
|
+
// with slightly different field names. Preserve the real invocation input
|
|
868
|
+
// so the dashboard can show more than a bare "tool" label.
|
|
869
|
+
const call = extractToolCall(raw);
|
|
870
|
+
if (call) {
|
|
871
|
+
payload.name = call.name;
|
|
872
|
+
if (call.params !== undefined && !isEmptyRecord(call.params))
|
|
873
|
+
payload.params = call.params;
|
|
874
|
+
if (call.id)
|
|
875
|
+
payload.id = call.id;
|
|
876
|
+
if (call.status)
|
|
877
|
+
payload.status = call.status;
|
|
883
878
|
}
|
|
884
879
|
return { kind: "tool_call", seq, payload };
|
|
885
880
|
}
|
|
886
881
|
if (kind === "tool_result") {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
resultStr = tr.content;
|
|
895
|
-
}
|
|
896
|
-
else if (Array.isArray(tr.content)) {
|
|
897
|
-
resultStr = tr.content
|
|
898
|
-
.map((c) => (typeof c?.text === "string" ? c.text : JSON.stringify(c)))
|
|
899
|
-
.join("\n");
|
|
900
|
-
}
|
|
901
|
-
payload.result = resultStr;
|
|
902
|
-
if (typeof tr.tool_use_id === "string")
|
|
903
|
-
payload.tool_use_id = tr.tool_use_id;
|
|
904
|
-
}
|
|
905
|
-
else if (raw?.item && typeof raw.item === "object") {
|
|
906
|
-
payload.name = typeof raw.item.type === "string" ? raw.item.type : "tool";
|
|
907
|
-
if (typeof raw.item.id === "string")
|
|
908
|
-
payload.tool_use_id = raw.item.id;
|
|
909
|
-
const result = codexToolResult(raw.item);
|
|
910
|
-
if (result)
|
|
911
|
-
payload.result = result;
|
|
882
|
+
const result = extractToolResult(raw);
|
|
883
|
+
if (result) {
|
|
884
|
+
if (result.name)
|
|
885
|
+
payload.name = result.name;
|
|
886
|
+
payload.result = result.result;
|
|
887
|
+
if (result.id)
|
|
888
|
+
payload.tool_use_id = result.id;
|
|
912
889
|
}
|
|
913
890
|
return { kind: "tool_result", seq, payload };
|
|
914
891
|
}
|
|
@@ -965,6 +942,168 @@ function isTerminalRuntimeBlock(raw) {
|
|
|
965
942
|
terminal === "turn.done" ||
|
|
966
943
|
terminal === "done");
|
|
967
944
|
}
|
|
945
|
+
function extractToolCall(raw) {
|
|
946
|
+
const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
|
|
947
|
+
const tu = contents.find((c) => c?.type === "tool_use");
|
|
948
|
+
if (tu) {
|
|
949
|
+
return {
|
|
950
|
+
name: stringField(tu, "name") ?? "tool",
|
|
951
|
+
params: parseMaybeJson(tu.input ?? tu.arguments),
|
|
952
|
+
id: stringField(tu, "id"),
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
const deepseek = extractDeepseekToolCall(raw);
|
|
956
|
+
if (deepseek)
|
|
957
|
+
return deepseek;
|
|
958
|
+
const item = raw?.item;
|
|
959
|
+
if (item && typeof item === "object") {
|
|
960
|
+
const params = codexToolParams(item);
|
|
961
|
+
return {
|
|
962
|
+
name: stringField(item, "type") ?? stringField(item, "name") ?? "tool",
|
|
963
|
+
params,
|
|
964
|
+
id: stringField(item, "id"),
|
|
965
|
+
status: stringField(item, "status"),
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
const toolCalls = Array.isArray(raw?.tool_calls) ? raw.tool_calls : [];
|
|
969
|
+
const toolCall = toolCalls.find((t) => t && typeof t === "object");
|
|
970
|
+
if (toolCall) {
|
|
971
|
+
const fn = toolCall.function && typeof toolCall.function === "object" ? toolCall.function : undefined;
|
|
972
|
+
return {
|
|
973
|
+
name: stringField(fn, "name") ?? stringField(toolCall, "name") ?? "tool",
|
|
974
|
+
params: parseMaybeJson(fn?.arguments ?? toolCall.arguments ?? toolCall.input ?? toolCall.rawInput),
|
|
975
|
+
id: stringField(toolCall, "id"),
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
const update = raw?.params?.update ?? raw?.update;
|
|
979
|
+
const acpTool = update?.toolCall ?? update?.tool_call ?? update?.tool;
|
|
980
|
+
if (acpTool && typeof acpTool === "object") {
|
|
981
|
+
return {
|
|
982
|
+
name: stringField(acpTool, "name") ?? stringField(update, "name") ?? "tool",
|
|
983
|
+
params: parseMaybeJson(acpTool.rawInput ??
|
|
984
|
+
acpTool.raw_input ??
|
|
985
|
+
acpTool.input ??
|
|
986
|
+
acpTool.arguments ??
|
|
987
|
+
acpTool.args ??
|
|
988
|
+
acpTool.params) ?? acpTool,
|
|
989
|
+
id: stringField(acpTool, "id") ?? stringField(update, "toolCallId"),
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
return null;
|
|
993
|
+
}
|
|
994
|
+
function extractToolResult(raw) {
|
|
995
|
+
const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
|
|
996
|
+
const tr = contents.find((c) => c?.type === "tool_result");
|
|
997
|
+
if (tr) {
|
|
998
|
+
return {
|
|
999
|
+
result: stringifyToolResult(tr.content),
|
|
1000
|
+
id: stringField(tr, "tool_use_id"),
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
const deepseek = extractDeepseekToolResult(raw);
|
|
1004
|
+
if (deepseek)
|
|
1005
|
+
return deepseek;
|
|
1006
|
+
const item = raw?.item;
|
|
1007
|
+
if (item && typeof item === "object") {
|
|
1008
|
+
const result = codexToolResult(item);
|
|
1009
|
+
return {
|
|
1010
|
+
name: stringField(item, "type") ?? stringField(item, "name"),
|
|
1011
|
+
result: result || stringifyToolResult(item),
|
|
1012
|
+
id: stringField(item, "id"),
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
if (raw?.role === "tool") {
|
|
1016
|
+
return {
|
|
1017
|
+
result: stringifyToolResult(raw.content),
|
|
1018
|
+
id: stringField(raw, "tool_call_id"),
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
const update = raw?.params?.update ?? raw?.update;
|
|
1022
|
+
const acpTool = update?.toolCall ?? update?.tool_call ?? update?.tool;
|
|
1023
|
+
if (acpTool && typeof acpTool === "object") {
|
|
1024
|
+
const result = acpTool.output ??
|
|
1025
|
+
acpTool.result ??
|
|
1026
|
+
acpTool.content ??
|
|
1027
|
+
acpTool.error ??
|
|
1028
|
+
update.content ??
|
|
1029
|
+
update;
|
|
1030
|
+
return {
|
|
1031
|
+
name: stringField(acpTool, "name") ?? stringField(update, "name"),
|
|
1032
|
+
result: stringifyToolResult(result),
|
|
1033
|
+
id: stringField(acpTool, "id") ?? stringField(update, "toolCallId"),
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
return null;
|
|
1037
|
+
}
|
|
1038
|
+
function extractDeepseekToolCall(raw) {
|
|
1039
|
+
const payload = raw?.payload;
|
|
1040
|
+
if (!payload || typeof payload !== "object")
|
|
1041
|
+
return null;
|
|
1042
|
+
if (raw?.event === "tool.started") {
|
|
1043
|
+
const tool = payload.tool && typeof payload.tool === "object" ? payload.tool : undefined;
|
|
1044
|
+
return {
|
|
1045
|
+
name: stringField(payload, "name") ?? stringField(tool, "name") ?? "tool",
|
|
1046
|
+
params: parseMaybeJson(payload.input ?? payload.arguments ?? payload.params ?? tool?.input ?? tool?.rawInput),
|
|
1047
|
+
id: stringField(payload, "id") ?? stringField(tool, "id"),
|
|
1048
|
+
status: stringField(payload, "status") ?? stringField(tool, "status"),
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
if (payload.event === "item.started") {
|
|
1052
|
+
const inner = payload.payload && typeof payload.payload === "object" ? payload.payload : {};
|
|
1053
|
+
const item = inner.item && typeof inner.item === "object" ? inner.item : undefined;
|
|
1054
|
+
const tool = inner.tool && typeof inner.tool === "object" ? inner.tool : item?.tool;
|
|
1055
|
+
return {
|
|
1056
|
+
name: stringField(tool, "name") ??
|
|
1057
|
+
stringField(inner, "name") ??
|
|
1058
|
+
stringField(item, "name") ??
|
|
1059
|
+
stringField(item, "type") ??
|
|
1060
|
+
"tool",
|
|
1061
|
+
params: parseMaybeJson(tool?.input ??
|
|
1062
|
+
tool?.rawInput ??
|
|
1063
|
+
tool?.arguments ??
|
|
1064
|
+
inner.input ??
|
|
1065
|
+
item?.input ??
|
|
1066
|
+
item?.arguments) ?? tool ?? item,
|
|
1067
|
+
id: stringField(tool, "id") ?? stringField(inner, "id") ?? stringField(item, "id"),
|
|
1068
|
+
status: stringField(tool, "status") ?? stringField(inner, "status") ?? stringField(item, "status"),
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
return null;
|
|
1072
|
+
}
|
|
1073
|
+
function extractDeepseekToolResult(raw) {
|
|
1074
|
+
const payload = raw?.payload;
|
|
1075
|
+
if (!payload || typeof payload !== "object")
|
|
1076
|
+
return null;
|
|
1077
|
+
if (raw?.event === "tool.completed") {
|
|
1078
|
+
const result = payload.output ?? payload.result ?? payload.content ?? payload.error ?? payload;
|
|
1079
|
+
return {
|
|
1080
|
+
name: stringField(payload, "name"),
|
|
1081
|
+
result: stringifyToolResult(result),
|
|
1082
|
+
id: stringField(payload, "id"),
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
if (payload.event === "item.completed" || payload.event === "item.failed") {
|
|
1086
|
+
const inner = payload.payload && typeof payload.payload === "object" ? payload.payload : {};
|
|
1087
|
+
const item = inner.item && typeof inner.item === "object" ? inner.item : undefined;
|
|
1088
|
+
const result = item?.output ??
|
|
1089
|
+
item?.result ??
|
|
1090
|
+
item?.content ??
|
|
1091
|
+
item?.detail ??
|
|
1092
|
+
item?.summary ??
|
|
1093
|
+
item?.error ??
|
|
1094
|
+
inner.output ??
|
|
1095
|
+
inner.result ??
|
|
1096
|
+
inner.error ??
|
|
1097
|
+
item ??
|
|
1098
|
+
inner;
|
|
1099
|
+
return {
|
|
1100
|
+
name: stringField(item, "name") ?? stringField(item, "type") ?? stringField(inner, "name"),
|
|
1101
|
+
result: stringifyToolResult(result),
|
|
1102
|
+
id: stringField(item, "id") ?? stringField(inner, "id"),
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
return null;
|
|
1106
|
+
}
|
|
968
1107
|
function formatBlockDetails(raw) {
|
|
969
1108
|
if (!raw || typeof raw !== "object")
|
|
970
1109
|
return "";
|
|
@@ -1034,6 +1173,52 @@ function codexToolResult(item) {
|
|
|
1034
1173
|
}
|
|
1035
1174
|
return parts.join("\n");
|
|
1036
1175
|
}
|
|
1176
|
+
function stringifyToolResult(value) {
|
|
1177
|
+
if (value == null)
|
|
1178
|
+
return "";
|
|
1179
|
+
if (typeof value === "string")
|
|
1180
|
+
return value;
|
|
1181
|
+
if (Array.isArray(value)) {
|
|
1182
|
+
return value
|
|
1183
|
+
.map((c) => {
|
|
1184
|
+
if (typeof c === "string")
|
|
1185
|
+
return c;
|
|
1186
|
+
if (typeof c?.text === "string")
|
|
1187
|
+
return c.text;
|
|
1188
|
+
return stringifyToolResult(c);
|
|
1189
|
+
})
|
|
1190
|
+
.filter(Boolean)
|
|
1191
|
+
.join("\n");
|
|
1192
|
+
}
|
|
1193
|
+
try {
|
|
1194
|
+
return JSON.stringify(value, null, 2);
|
|
1195
|
+
}
|
|
1196
|
+
catch {
|
|
1197
|
+
return String(value);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
function parseMaybeJson(value) {
|
|
1201
|
+
if (typeof value !== "string")
|
|
1202
|
+
return value;
|
|
1203
|
+
const trimmed = value.trim();
|
|
1204
|
+
if (!trimmed)
|
|
1205
|
+
return value;
|
|
1206
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("["))
|
|
1207
|
+
return value;
|
|
1208
|
+
try {
|
|
1209
|
+
return JSON.parse(trimmed);
|
|
1210
|
+
}
|
|
1211
|
+
catch {
|
|
1212
|
+
return value;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
function isEmptyRecord(value) {
|
|
1216
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
1217
|
+
}
|
|
1218
|
+
function stringField(obj, key) {
|
|
1219
|
+
const value = obj?.[key];
|
|
1220
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
1221
|
+
}
|
|
1037
1222
|
function extractContentText(content) {
|
|
1038
1223
|
if (!content)
|
|
1039
1224
|
return "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botcord/daemon",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.77",
|
|
4
4
|
"description": "BotCord local daemon — bridges Hub inbox push to local Claude Code / Codex / Gemini CLIs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@botcord/cli": "^0.1.7",
|
|
31
|
-
"@botcord/protocol-core": "
|
|
31
|
+
"@botcord/protocol-core": "^0.2.9",
|
|
32
32
|
"@larksuiteoapi/node-sdk": "^1.63.1",
|
|
33
33
|
"ws": "^8.20.1"
|
|
34
34
|
},
|
|
@@ -769,6 +769,30 @@ describe("createBotCordChannel — streamBlock()", () => {
|
|
|
769
769
|
});
|
|
770
770
|
});
|
|
771
771
|
|
|
772
|
+
it("normalizes DeepSeek tool input so the dashboard can expand it", () => {
|
|
773
|
+
expect(
|
|
774
|
+
__normalizeBlockForHubForTests(
|
|
775
|
+
{
|
|
776
|
+
kind: "tool_use",
|
|
777
|
+
seq: 4,
|
|
778
|
+
raw: {
|
|
779
|
+
event: "tool.started",
|
|
780
|
+
payload: { id: "tool_1", name: "exec_shell", input: { cmd: "pwd" } },
|
|
781
|
+
},
|
|
782
|
+
},
|
|
783
|
+
4,
|
|
784
|
+
),
|
|
785
|
+
).toEqual({
|
|
786
|
+
kind: "tool_call",
|
|
787
|
+
seq: 4,
|
|
788
|
+
payload: {
|
|
789
|
+
id: "tool_1",
|
|
790
|
+
name: "exec_shell",
|
|
791
|
+
params: { cmd: "pwd" },
|
|
792
|
+
},
|
|
793
|
+
});
|
|
794
|
+
});
|
|
795
|
+
|
|
772
796
|
it("POSTs to /hub/stream-block with the right trace_id + block", async () => {
|
|
773
797
|
const fetchSpy = vi.fn().mockResolvedValue(new Response(null, { status: 204 }));
|
|
774
798
|
const realFetch = globalThis.fetch;
|
|
@@ -153,10 +153,11 @@ function defaultClientFactory(input: {
|
|
|
153
153
|
*/
|
|
154
154
|
function isOwnerTrust(msg: InboxMessage): boolean {
|
|
155
155
|
if (msg.room_id?.startsWith(OWNER_CHAT_PREFIX)) return true;
|
|
156
|
-
|
|
156
|
+
const sourceType = msg.source_type as string | undefined;
|
|
157
|
+
if (sourceType === "dashboard_user_chat") return true;
|
|
157
158
|
// Cloud Agent run tasks are Hub-issued on the user's behalf, same
|
|
158
159
|
// trust posture as owner chat.
|
|
159
|
-
if (
|
|
160
|
+
if (sourceType === "cloud_agent_run") return true;
|
|
160
161
|
return false;
|
|
161
162
|
}
|
|
162
163
|
|
|
@@ -197,10 +198,11 @@ function normalizeInbox(
|
|
|
197
198
|
// run completes). All other envelope types (notification, system,
|
|
198
199
|
// contact_added/removed, …) are still filtered out — they belong in
|
|
199
200
|
// a separate push-notification path that daemon does not yet implement.
|
|
201
|
+
const envType = env.type as string;
|
|
200
202
|
if (
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
203
|
+
envType !== "message" &&
|
|
204
|
+
envType !== "contact_request" &&
|
|
205
|
+
envType !== "cloud_run"
|
|
204
206
|
)
|
|
205
207
|
return null;
|
|
206
208
|
if (!msg.room_id) return null;
|
|
@@ -214,8 +216,9 @@ function normalizeInbox(
|
|
|
214
216
|
|
|
215
217
|
const isDm = msg.room_id.startsWith(DM_ROOM_PREFIX);
|
|
216
218
|
const isOwnerChat = msg.room_id.startsWith(OWNER_CHAT_PREFIX);
|
|
219
|
+
const sourceType = msg.source_type as string | undefined;
|
|
217
220
|
const senderKind: "user" | "agent" =
|
|
218
|
-
ownerTrust ||
|
|
221
|
+
ownerTrust || sourceType === "dashboard_human_room" ? "user" : "agent";
|
|
219
222
|
|
|
220
223
|
const senderName = msg.source_user_name ?? undefined;
|
|
221
224
|
const threadId = msg.topic_id ?? msg.topic ?? null;
|
|
@@ -1005,45 +1008,25 @@ function normalizeBlockForHub(
|
|
|
1005
1008
|
}
|
|
1006
1009
|
|
|
1007
1010
|
if (kind === "tool_use") {
|
|
1008
|
-
// Claude Code
|
|
1009
|
-
//
|
|
1010
|
-
|
|
1011
|
-
const
|
|
1012
|
-
if (
|
|
1013
|
-
payload.name =
|
|
1014
|
-
if (
|
|
1015
|
-
if (
|
|
1016
|
-
|
|
1017
|
-
payload.name = typeof raw.item.type === "string" ? raw.item.type : "tool";
|
|
1018
|
-
const params = codexToolParams(raw.item);
|
|
1019
|
-
if (Object.keys(params).length > 0) payload.params = params;
|
|
1020
|
-
if (typeof raw.item.id === "string") payload.id = raw.item.id;
|
|
1021
|
-
if (typeof raw.item.status === "string") payload.status = raw.item.status;
|
|
1011
|
+
// Claude Code, Codex, DeepSeek TUI, Kimi, and ACP all expose tool calls
|
|
1012
|
+
// with slightly different field names. Preserve the real invocation input
|
|
1013
|
+
// so the dashboard can show more than a bare "tool" label.
|
|
1014
|
+
const call = extractToolCall(raw);
|
|
1015
|
+
if (call) {
|
|
1016
|
+
payload.name = call.name;
|
|
1017
|
+
if (call.params !== undefined && !isEmptyRecord(call.params)) payload.params = call.params;
|
|
1018
|
+
if (call.id) payload.id = call.id;
|
|
1019
|
+
if (call.status) payload.status = call.status;
|
|
1022
1020
|
}
|
|
1023
1021
|
return { kind: "tool_call", seq, payload };
|
|
1024
1022
|
}
|
|
1025
1023
|
|
|
1026
1024
|
if (kind === "tool_result") {
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
let resultStr = "";
|
|
1033
|
-
if (typeof tr.content === "string") {
|
|
1034
|
-
resultStr = tr.content;
|
|
1035
|
-
} else if (Array.isArray(tr.content)) {
|
|
1036
|
-
resultStr = tr.content
|
|
1037
|
-
.map((c: any) => (typeof c?.text === "string" ? c.text : JSON.stringify(c)))
|
|
1038
|
-
.join("\n");
|
|
1039
|
-
}
|
|
1040
|
-
payload.result = resultStr;
|
|
1041
|
-
if (typeof tr.tool_use_id === "string") payload.tool_use_id = tr.tool_use_id;
|
|
1042
|
-
} else if (raw?.item && typeof raw.item === "object") {
|
|
1043
|
-
payload.name = typeof raw.item.type === "string" ? raw.item.type : "tool";
|
|
1044
|
-
if (typeof raw.item.id === "string") payload.tool_use_id = raw.item.id;
|
|
1045
|
-
const result = codexToolResult(raw.item);
|
|
1046
|
-
if (result) payload.result = result;
|
|
1025
|
+
const result = extractToolResult(raw);
|
|
1026
|
+
if (result) {
|
|
1027
|
+
if (result.name) payload.name = result.name;
|
|
1028
|
+
payload.result = result.result;
|
|
1029
|
+
if (result.id) payload.tool_use_id = result.id;
|
|
1047
1030
|
}
|
|
1048
1031
|
return { kind: "tool_result", seq, payload };
|
|
1049
1032
|
}
|
|
@@ -1097,6 +1080,191 @@ function isTerminalRuntimeBlock(raw: any): boolean {
|
|
|
1097
1080
|
);
|
|
1098
1081
|
}
|
|
1099
1082
|
|
|
1083
|
+
function extractToolCall(raw: any): { name: string; params?: unknown; id?: string; status?: string } | null {
|
|
1084
|
+
const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
|
|
1085
|
+
const tu = contents.find((c: any) => c?.type === "tool_use");
|
|
1086
|
+
if (tu) {
|
|
1087
|
+
return {
|
|
1088
|
+
name: stringField(tu, "name") ?? "tool",
|
|
1089
|
+
params: parseMaybeJson(tu.input ?? tu.arguments),
|
|
1090
|
+
id: stringField(tu, "id"),
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
const deepseek = extractDeepseekToolCall(raw);
|
|
1095
|
+
if (deepseek) return deepseek;
|
|
1096
|
+
|
|
1097
|
+
const item = raw?.item;
|
|
1098
|
+
if (item && typeof item === "object") {
|
|
1099
|
+
const params = codexToolParams(item);
|
|
1100
|
+
return {
|
|
1101
|
+
name: stringField(item, "type") ?? stringField(item, "name") ?? "tool",
|
|
1102
|
+
params,
|
|
1103
|
+
id: stringField(item, "id"),
|
|
1104
|
+
status: stringField(item, "status"),
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
const toolCalls = Array.isArray(raw?.tool_calls) ? raw.tool_calls : [];
|
|
1109
|
+
const toolCall = toolCalls.find((t: any) => t && typeof t === "object");
|
|
1110
|
+
if (toolCall) {
|
|
1111
|
+
const fn = toolCall.function && typeof toolCall.function === "object" ? toolCall.function : undefined;
|
|
1112
|
+
return {
|
|
1113
|
+
name: stringField(fn, "name") ?? stringField(toolCall, "name") ?? "tool",
|
|
1114
|
+
params: parseMaybeJson(fn?.arguments ?? toolCall.arguments ?? toolCall.input ?? toolCall.rawInput),
|
|
1115
|
+
id: stringField(toolCall, "id"),
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
const update = raw?.params?.update ?? raw?.update;
|
|
1120
|
+
const acpTool = update?.toolCall ?? update?.tool_call ?? update?.tool;
|
|
1121
|
+
if (acpTool && typeof acpTool === "object") {
|
|
1122
|
+
return {
|
|
1123
|
+
name: stringField(acpTool, "name") ?? stringField(update, "name") ?? "tool",
|
|
1124
|
+
params: parseMaybeJson(
|
|
1125
|
+
acpTool.rawInput ??
|
|
1126
|
+
acpTool.raw_input ??
|
|
1127
|
+
acpTool.input ??
|
|
1128
|
+
acpTool.arguments ??
|
|
1129
|
+
acpTool.args ??
|
|
1130
|
+
acpTool.params,
|
|
1131
|
+
) ?? acpTool,
|
|
1132
|
+
id: stringField(acpTool, "id") ?? stringField(update, "toolCallId"),
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
return null;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
function extractToolResult(raw: any): { name?: string; result: string; id?: string } | null {
|
|
1140
|
+
const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
|
|
1141
|
+
const tr = contents.find((c: any) => c?.type === "tool_result");
|
|
1142
|
+
if (tr) {
|
|
1143
|
+
return {
|
|
1144
|
+
result: stringifyToolResult(tr.content),
|
|
1145
|
+
id: stringField(tr, "tool_use_id"),
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const deepseek = extractDeepseekToolResult(raw);
|
|
1150
|
+
if (deepseek) return deepseek;
|
|
1151
|
+
|
|
1152
|
+
const item = raw?.item;
|
|
1153
|
+
if (item && typeof item === "object") {
|
|
1154
|
+
const result = codexToolResult(item);
|
|
1155
|
+
return {
|
|
1156
|
+
name: stringField(item, "type") ?? stringField(item, "name"),
|
|
1157
|
+
result: result || stringifyToolResult(item),
|
|
1158
|
+
id: stringField(item, "id"),
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
if (raw?.role === "tool") {
|
|
1163
|
+
return {
|
|
1164
|
+
result: stringifyToolResult(raw.content),
|
|
1165
|
+
id: stringField(raw, "tool_call_id"),
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
const update = raw?.params?.update ?? raw?.update;
|
|
1170
|
+
const acpTool = update?.toolCall ?? update?.tool_call ?? update?.tool;
|
|
1171
|
+
if (acpTool && typeof acpTool === "object") {
|
|
1172
|
+
const result =
|
|
1173
|
+
acpTool.output ??
|
|
1174
|
+
acpTool.result ??
|
|
1175
|
+
acpTool.content ??
|
|
1176
|
+
acpTool.error ??
|
|
1177
|
+
update.content ??
|
|
1178
|
+
update;
|
|
1179
|
+
return {
|
|
1180
|
+
name: stringField(acpTool, "name") ?? stringField(update, "name"),
|
|
1181
|
+
result: stringifyToolResult(result),
|
|
1182
|
+
id: stringField(acpTool, "id") ?? stringField(update, "toolCallId"),
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
return null;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function extractDeepseekToolCall(raw: any): { name: string; params?: unknown; id?: string; status?: string } | null {
|
|
1190
|
+
const payload = raw?.payload;
|
|
1191
|
+
if (!payload || typeof payload !== "object") return null;
|
|
1192
|
+
|
|
1193
|
+
if (raw?.event === "tool.started") {
|
|
1194
|
+
const tool = payload.tool && typeof payload.tool === "object" ? payload.tool : undefined;
|
|
1195
|
+
return {
|
|
1196
|
+
name: stringField(payload, "name") ?? stringField(tool, "name") ?? "tool",
|
|
1197
|
+
params: parseMaybeJson(payload.input ?? payload.arguments ?? payload.params ?? tool?.input ?? tool?.rawInput),
|
|
1198
|
+
id: stringField(payload, "id") ?? stringField(tool, "id"),
|
|
1199
|
+
status: stringField(payload, "status") ?? stringField(tool, "status"),
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
if (payload.event === "item.started") {
|
|
1204
|
+
const inner = payload.payload && typeof payload.payload === "object" ? payload.payload : {};
|
|
1205
|
+
const item = inner.item && typeof inner.item === "object" ? inner.item : undefined;
|
|
1206
|
+
const tool = inner.tool && typeof inner.tool === "object" ? inner.tool : item?.tool;
|
|
1207
|
+
return {
|
|
1208
|
+
name:
|
|
1209
|
+
stringField(tool, "name") ??
|
|
1210
|
+
stringField(inner, "name") ??
|
|
1211
|
+
stringField(item, "name") ??
|
|
1212
|
+
stringField(item, "type") ??
|
|
1213
|
+
"tool",
|
|
1214
|
+
params: parseMaybeJson(
|
|
1215
|
+
tool?.input ??
|
|
1216
|
+
tool?.rawInput ??
|
|
1217
|
+
tool?.arguments ??
|
|
1218
|
+
inner.input ??
|
|
1219
|
+
item?.input ??
|
|
1220
|
+
item?.arguments,
|
|
1221
|
+
) ?? tool ?? item,
|
|
1222
|
+
id: stringField(tool, "id") ?? stringField(inner, "id") ?? stringField(item, "id"),
|
|
1223
|
+
status: stringField(tool, "status") ?? stringField(inner, "status") ?? stringField(item, "status"),
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
return null;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
function extractDeepseekToolResult(raw: any): { name?: string; result: string; id?: string } | null {
|
|
1231
|
+
const payload = raw?.payload;
|
|
1232
|
+
if (!payload || typeof payload !== "object") return null;
|
|
1233
|
+
|
|
1234
|
+
if (raw?.event === "tool.completed") {
|
|
1235
|
+
const result = payload.output ?? payload.result ?? payload.content ?? payload.error ?? payload;
|
|
1236
|
+
return {
|
|
1237
|
+
name: stringField(payload, "name"),
|
|
1238
|
+
result: stringifyToolResult(result),
|
|
1239
|
+
id: stringField(payload, "id"),
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
if (payload.event === "item.completed" || payload.event === "item.failed") {
|
|
1244
|
+
const inner = payload.payload && typeof payload.payload === "object" ? payload.payload : {};
|
|
1245
|
+
const item = inner.item && typeof inner.item === "object" ? inner.item : undefined;
|
|
1246
|
+
const result =
|
|
1247
|
+
item?.output ??
|
|
1248
|
+
item?.result ??
|
|
1249
|
+
item?.content ??
|
|
1250
|
+
item?.detail ??
|
|
1251
|
+
item?.summary ??
|
|
1252
|
+
item?.error ??
|
|
1253
|
+
inner.output ??
|
|
1254
|
+
inner.result ??
|
|
1255
|
+
inner.error ??
|
|
1256
|
+
item ??
|
|
1257
|
+
inner;
|
|
1258
|
+
return {
|
|
1259
|
+
name: stringField(item, "name") ?? stringField(item, "type") ?? stringField(inner, "name"),
|
|
1260
|
+
result: stringifyToolResult(result),
|
|
1261
|
+
id: stringField(item, "id") ?? stringField(inner, "id"),
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1100
1268
|
function formatBlockDetails(raw: unknown): string {
|
|
1101
1269
|
if (!raw || typeof raw !== "object") return "";
|
|
1102
1270
|
const r = raw as any;
|
|
@@ -1168,6 +1336,47 @@ function codexToolResult(item: Record<string, unknown>): string {
|
|
|
1168
1336
|
return parts.join("\n");
|
|
1169
1337
|
}
|
|
1170
1338
|
|
|
1339
|
+
function stringifyToolResult(value: unknown): string {
|
|
1340
|
+
if (value == null) return "";
|
|
1341
|
+
if (typeof value === "string") return value;
|
|
1342
|
+
if (Array.isArray(value)) {
|
|
1343
|
+
return value
|
|
1344
|
+
.map((c: any) => {
|
|
1345
|
+
if (typeof c === "string") return c;
|
|
1346
|
+
if (typeof c?.text === "string") return c.text;
|
|
1347
|
+
return stringifyToolResult(c);
|
|
1348
|
+
})
|
|
1349
|
+
.filter(Boolean)
|
|
1350
|
+
.join("\n");
|
|
1351
|
+
}
|
|
1352
|
+
try {
|
|
1353
|
+
return JSON.stringify(value, null, 2);
|
|
1354
|
+
} catch {
|
|
1355
|
+
return String(value);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
function parseMaybeJson(value: unknown): unknown {
|
|
1360
|
+
if (typeof value !== "string") return value;
|
|
1361
|
+
const trimmed = value.trim();
|
|
1362
|
+
if (!trimmed) return value;
|
|
1363
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return value;
|
|
1364
|
+
try {
|
|
1365
|
+
return JSON.parse(trimmed);
|
|
1366
|
+
} catch {
|
|
1367
|
+
return value;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
function isEmptyRecord(value: unknown): boolean {
|
|
1372
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
function stringField(obj: any, key: string): string | undefined {
|
|
1376
|
+
const value = obj?.[key];
|
|
1377
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1171
1380
|
function extractContentText(content: unknown): string {
|
|
1172
1381
|
if (!content) return "";
|
|
1173
1382
|
if (typeof content === "string") return content;
|