@botcord/daemon 0.2.76 → 0.2.78
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/agent-discovery.d.ts +6 -0
- package/dist/agent-discovery.js +6 -0
- package/dist/daemon-config-map.d.ts +6 -0
- package/dist/daemon-config-map.js +5 -4
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +11 -1
- package/dist/gateway/channels/botcord.js +236 -51
- package/dist/gateway/runtimes/deepseek-tui.js +55 -7
- package/dist/provision.d.ts +2 -0
- package/dist/provision.js +66 -1
- package/dist/runtime-models.d.ts +17 -0
- package/dist/runtime-models.js +953 -0
- package/dist/runtime-route-options.d.ts +7 -0
- package/dist/runtime-route-options.js +45 -0
- package/package.json +2 -2
- package/src/__tests__/daemon-config-map.test.ts +26 -1
- package/src/__tests__/provision.test.ts +59 -0
- package/src/__tests__/runtime-discovery.test.ts +68 -9
- package/src/__tests__/runtime-models.test.ts +333 -0
- package/src/agent-discovery.ts +9 -0
- package/src/daemon-config-map.ts +17 -4
- package/src/daemon.ts +15 -3
- package/src/gateway/__tests__/botcord-channel.test.ts +24 -0
- package/src/gateway/__tests__/deepseek-tui-adapter.test.ts +30 -1
- package/src/gateway/channels/botcord.ts +249 -40
- package/src/gateway/runtimes/deepseek-tui.ts +49 -7
- package/src/provision.ts +69 -4
- package/src/runtime-models.ts +972 -0
- package/src/runtime-route-options.ts +52 -0
package/src/daemon.ts
CHANGED
|
@@ -684,7 +684,7 @@ export async function startDaemon(opts: DaemonRuntimeOptions): Promise<DaemonHan
|
|
|
684
684
|
*/
|
|
685
685
|
export interface BootBackfillResult {
|
|
686
686
|
credentialPathByAgentId: Map<string, string>;
|
|
687
|
-
agentRuntimes: Record<string, { runtime?: string; cwd?: string; openclawGateway?: string; openclawAgent?: string; hermesProfile?: string }>;
|
|
687
|
+
agentRuntimes: Record<string, { runtime?: string; runtimeModel?: string; reasoningEffort?: string; thinking?: boolean; cwd?: string; openclawGateway?: string; openclawAgent?: string; hermesProfile?: string }>;
|
|
688
688
|
}
|
|
689
689
|
|
|
690
690
|
/**
|
|
@@ -703,13 +703,25 @@ export function backfillBootAgents(
|
|
|
703
703
|
): BootBackfillResult {
|
|
704
704
|
const ensure = opts.ensure ?? ensureAgentWorkspace;
|
|
705
705
|
const credentialPathByAgentId = new Map<string, string>();
|
|
706
|
-
const agentRuntimes: Record<string, { runtime?: string; cwd?: string }> = {};
|
|
706
|
+
const agentRuntimes: Record<string, { runtime?: string; runtimeModel?: string; reasoningEffort?: string; thinking?: boolean; cwd?: string; openclawGateway?: string; openclawAgent?: string; hermesProfile?: string }> = {};
|
|
707
707
|
const failed: string[] = [];
|
|
708
708
|
for (const a of agents) {
|
|
709
709
|
if (a.credentialsFile) credentialPathByAgentId.set(a.agentId, a.credentialsFile);
|
|
710
|
-
if (
|
|
710
|
+
if (
|
|
711
|
+
a.runtime ||
|
|
712
|
+
a.runtimeModel ||
|
|
713
|
+
a.reasoningEffort ||
|
|
714
|
+
typeof a.thinking === "boolean" ||
|
|
715
|
+
a.cwd ||
|
|
716
|
+
a.openclawGateway ||
|
|
717
|
+
a.openclawAgent ||
|
|
718
|
+
a.hermesProfile
|
|
719
|
+
) {
|
|
711
720
|
agentRuntimes[a.agentId] = {
|
|
712
721
|
...(a.runtime ? { runtime: a.runtime } : {}),
|
|
722
|
+
...(a.runtimeModel ? { runtimeModel: a.runtimeModel } : {}),
|
|
723
|
+
...(a.reasoningEffort ? { reasoningEffort: a.reasoningEffort } : {}),
|
|
724
|
+
...(typeof a.thinking === "boolean" ? { thinking: a.thinking } : {}),
|
|
713
725
|
...(a.cwd ? { cwd: a.cwd } : {}),
|
|
714
726
|
...(a.openclawGateway ? { openclawGateway: a.openclawGateway } : {}),
|
|
715
727
|
...(a.openclawAgent ? { openclawAgent: a.openclawAgent } : {}),
|
|
@@ -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;
|
|
@@ -106,7 +106,12 @@ async function startMockDeepseekServer(opts?: {
|
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
function runAdapter(
|
|
109
|
+
function runAdapter(
|
|
110
|
+
serverUrl: string,
|
|
111
|
+
authToken: string,
|
|
112
|
+
sessionId: string | null = null,
|
|
113
|
+
extraArgs?: string[],
|
|
114
|
+
) {
|
|
110
115
|
const adapter = new DeepseekTuiAdapter({ serverUrl, authToken });
|
|
111
116
|
const ctrl = new AbortController();
|
|
112
117
|
const blocks: string[] = [];
|
|
@@ -118,6 +123,7 @@ function runAdapter(serverUrl: string, authToken: string, sessionId: string | nu
|
|
|
118
123
|
cwd: tmpRoot,
|
|
119
124
|
signal: ctrl.signal,
|
|
120
125
|
trustLevel: "owner",
|
|
126
|
+
extraArgs,
|
|
121
127
|
systemContext: "runtime memory",
|
|
122
128
|
onBlock: (b) => blocks.push(b.kind),
|
|
123
129
|
onStatus: (e) => {
|
|
@@ -184,6 +190,29 @@ describe("DeepseekTuiAdapter", () => {
|
|
|
184
190
|
}
|
|
185
191
|
});
|
|
186
192
|
|
|
193
|
+
it("passes selected model and reasoning effort through HTTP payloads", async () => {
|
|
194
|
+
const server = await startMockDeepseekServer();
|
|
195
|
+
try {
|
|
196
|
+
const { result } = runAdapter(server.baseUrl, server.token, null, [
|
|
197
|
+
"--model",
|
|
198
|
+
"deepseek-v4-pro",
|
|
199
|
+
"--reasoning-effort",
|
|
200
|
+
"auto",
|
|
201
|
+
]);
|
|
202
|
+
await result;
|
|
203
|
+
expect(server.calls.find((c) => c.method === "POST" && c.url === "/v1/threads")?.body).toMatchObject({
|
|
204
|
+
model: "deepseek-v4-pro",
|
|
205
|
+
reasoning_effort: "auto",
|
|
206
|
+
});
|
|
207
|
+
expect(server.calls.find((c) => c.method === "POST" && c.url.endsWith("/turns"))?.body).toMatchObject({
|
|
208
|
+
model: "deepseek-v4-pro",
|
|
209
|
+
reasoning_effort: "auto",
|
|
210
|
+
});
|
|
211
|
+
} finally {
|
|
212
|
+
await server.close();
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
187
216
|
it("clears stale session ids when DeepSeek reports the thread missing", async () => {
|
|
188
217
|
const server = await startMockDeepseekServer({ threadId: "thr_other" });
|
|
189
218
|
try {
|
|
@@ -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;
|
|
@@ -260,6 +260,9 @@ export class DeepseekTuiAdapter implements RuntimeAdapter {
|
|
|
260
260
|
auto_approve: opts.trustLevel !== "public",
|
|
261
261
|
archived: false,
|
|
262
262
|
};
|
|
263
|
+
const selection = parseDeepseekRuntimeSelection(opts.extraArgs);
|
|
264
|
+
if (selection.model) body.model = selection.model;
|
|
265
|
+
if (selection.reasoningEffort) body.reasoning_effort = selection.reasoningEffort;
|
|
263
266
|
if (opts.systemContext) body.system_prompt = opts.systemContext;
|
|
264
267
|
const res = await this.requestJson<any>(`${baseUrl}/v1/threads`, {
|
|
265
268
|
method: "POST",
|
|
@@ -306,18 +309,22 @@ export class DeepseekTuiAdapter implements RuntimeAdapter {
|
|
|
306
309
|
});
|
|
307
310
|
let turnId = "";
|
|
308
311
|
try {
|
|
312
|
+
const selection = parseDeepseekRuntimeSelection(opts.extraArgs);
|
|
313
|
+
const body: Record<string, unknown> = {
|
|
314
|
+
prompt: opts.text,
|
|
315
|
+
mode: "agent",
|
|
316
|
+
allow_shell: opts.trustLevel !== "public",
|
|
317
|
+
trust_mode: opts.trustLevel !== "public",
|
|
318
|
+
auto_approve: opts.trustLevel !== "public",
|
|
319
|
+
};
|
|
320
|
+
if (selection.model) body.model = selection.model;
|
|
321
|
+
if (selection.reasoningEffort) body.reasoning_effort = selection.reasoningEffort;
|
|
309
322
|
const started = await this.requestJson<any>(
|
|
310
323
|
`${baseUrl}/v1/threads/${encodeURIComponent(threadId)}/turns`,
|
|
311
324
|
{
|
|
312
325
|
method: "POST",
|
|
313
326
|
headers,
|
|
314
|
-
body: JSON.stringify(
|
|
315
|
-
prompt: opts.text,
|
|
316
|
-
mode: "agent",
|
|
317
|
-
allow_shell: opts.trustLevel !== "public",
|
|
318
|
-
trust_mode: opts.trustLevel !== "public",
|
|
319
|
-
auto_approve: opts.trustLevel !== "public",
|
|
320
|
-
}),
|
|
327
|
+
body: JSON.stringify(body),
|
|
321
328
|
signal,
|
|
322
329
|
},
|
|
323
330
|
);
|
|
@@ -535,6 +542,41 @@ function authHeaders(token: string): HeadersInit {
|
|
|
535
542
|
return token ? { authorization: `Bearer ${token}` } : {};
|
|
536
543
|
}
|
|
537
544
|
|
|
545
|
+
function parseDeepseekRuntimeSelection(
|
|
546
|
+
extraArgs: string[] | undefined,
|
|
547
|
+
): { model?: string; reasoningEffort?: string } {
|
|
548
|
+
const out: { model?: string; reasoningEffort?: string } = {};
|
|
549
|
+
if (!extraArgs?.length) return out;
|
|
550
|
+
for (let i = 0; i < extraArgs.length; i += 1) {
|
|
551
|
+
const arg = extraArgs[i]!;
|
|
552
|
+
if (arg === "--model") {
|
|
553
|
+
const value = nextArgValue(extraArgs, i);
|
|
554
|
+
if (value !== undefined) {
|
|
555
|
+
out.model = value;
|
|
556
|
+
i += 1;
|
|
557
|
+
}
|
|
558
|
+
} else if (arg.startsWith("--model=")) {
|
|
559
|
+
out.model = arg.slice("--model=".length);
|
|
560
|
+
} else if (arg === "--reasoning-effort") {
|
|
561
|
+
const value = nextArgValue(extraArgs, i);
|
|
562
|
+
if (value !== undefined) {
|
|
563
|
+
out.reasoningEffort = value;
|
|
564
|
+
i += 1;
|
|
565
|
+
}
|
|
566
|
+
} else if (arg.startsWith("--reasoning-effort=")) {
|
|
567
|
+
out.reasoningEffort = arg.slice("--reasoning-effort=".length);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return out;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function nextArgValue(args: string[], index: number): string | undefined {
|
|
574
|
+
const next = args[index + 1];
|
|
575
|
+
if (typeof next !== "string") return undefined;
|
|
576
|
+
if (!next.startsWith("-")) return next;
|
|
577
|
+
return /^-\d/.test(next) ? next : undefined;
|
|
578
|
+
}
|
|
579
|
+
|
|
538
580
|
function poolKey(opts: RuntimeRunOptions): string {
|
|
539
581
|
return opts.accountId || "default";
|
|
540
582
|
}
|
package/src/provision.ts
CHANGED
|
@@ -72,6 +72,11 @@ import {
|
|
|
72
72
|
import { log as daemonLog } from "./log.js";
|
|
73
73
|
import { discoverAgentCredentials } from "./agent-discovery.js";
|
|
74
74
|
import { resolveMemoryDir } from "./working-memory.js";
|
|
75
|
+
import { discoverRuntimeModelCatalog } from "./runtime-models.js";
|
|
76
|
+
import {
|
|
77
|
+
buildRuntimeSelectionExtraArgs,
|
|
78
|
+
mergeRuntimeExtraArgs,
|
|
79
|
+
} from "./runtime-route-options.js";
|
|
75
80
|
|
|
76
81
|
/**
|
|
77
82
|
* Information passed to {@link OnAgentInstalledHook} after a successful
|
|
@@ -1057,6 +1062,11 @@ function upsertManagedRouteForCredentials(
|
|
|
1057
1062
|
runtime: credentials.runtime ?? cfg.defaultRoute.adapter,
|
|
1058
1063
|
cwd: credentials.cwd ?? agentWorkspaceDir(credentials.agentId),
|
|
1059
1064
|
};
|
|
1065
|
+
const extraArgs = mergeRuntimeExtraArgs(
|
|
1066
|
+
cfg.defaultRoute.extraArgs,
|
|
1067
|
+
buildRuntimeSelectionExtraArgs(synthRoute.runtime, credentials),
|
|
1068
|
+
);
|
|
1069
|
+
if (extraArgs) synthRoute.extraArgs = extraArgs;
|
|
1060
1070
|
if (synthRoute.runtime === "openclaw-acp") {
|
|
1061
1071
|
const profile = (cfg.openclawGateways ?? []).find(
|
|
1062
1072
|
(g) => g.name === credentials.openclawGateway,
|
|
@@ -1169,6 +1179,10 @@ async function materializeCredentials(
|
|
|
1169
1179
|
if (c.token) record.token = c.token;
|
|
1170
1180
|
if (typeof c.tokenExpiresAt === "number") record.tokenExpiresAt = c.tokenExpiresAt;
|
|
1171
1181
|
if (runtime) record.runtime = runtime;
|
|
1182
|
+
const runtimeSelection = pickRuntimeSelection(params);
|
|
1183
|
+
if (runtimeSelection.runtimeModel) record.runtimeModel = runtimeSelection.runtimeModel;
|
|
1184
|
+
if (runtimeSelection.reasoningEffort) record.reasoningEffort = runtimeSelection.reasoningEffort;
|
|
1185
|
+
if (typeof runtimeSelection.thinking === "boolean") record.thinking = runtimeSelection.thinking;
|
|
1172
1186
|
record.cwd = cwd;
|
|
1173
1187
|
const openclawSel = pickOpenclawSelection(params);
|
|
1174
1188
|
if (openclawSel.gateway) record.openclawGateway = openclawSel.gateway;
|
|
@@ -1203,6 +1217,10 @@ async function materializeCredentials(
|
|
|
1203
1217
|
tokenExpiresAt: reg.expiresAt,
|
|
1204
1218
|
};
|
|
1205
1219
|
if (runtime) record.runtime = runtime;
|
|
1220
|
+
const runtimeSelection = pickRuntimeSelection(params);
|
|
1221
|
+
if (runtimeSelection.runtimeModel) record.runtimeModel = runtimeSelection.runtimeModel;
|
|
1222
|
+
if (runtimeSelection.reasoningEffort) record.reasoningEffort = runtimeSelection.reasoningEffort;
|
|
1223
|
+
if (typeof runtimeSelection.thinking === "boolean") record.thinking = runtimeSelection.thinking;
|
|
1206
1224
|
record.cwd = cwd;
|
|
1207
1225
|
const openclawSel = pickOpenclawSelection(params);
|
|
1208
1226
|
if (openclawSel.gateway) record.openclawGateway = openclawSel.gateway;
|
|
@@ -1784,6 +1802,10 @@ export function collectRuntimeSnapshot(opts: { force?: boolean } = {}): ListRunt
|
|
|
1784
1802
|
// style used above.
|
|
1785
1803
|
if (entry.result.version) record.version = entry.result.version;
|
|
1786
1804
|
if (entry.result.path) record.path = entry.result.path;
|
|
1805
|
+
const catalog = discoverRuntimeModelCatalog(entry);
|
|
1806
|
+
const models = catalog.models;
|
|
1807
|
+
if (models?.length) record.models = models.slice(0, RUNTIME_MODELS_CAP);
|
|
1808
|
+
if (catalog.parameters?.length) record.parameters = catalog.parameters.slice(0, RUNTIME_PARAMETERS_CAP);
|
|
1787
1809
|
// Gateway's probe surface doesn't expose an `error` string today — it
|
|
1788
1810
|
// already swallows throws into `{available: false}`. We leave the wire
|
|
1789
1811
|
// field blank in that case and let callers treat `!available` as reason
|
|
@@ -1841,6 +1863,8 @@ export function attachRuntimeHealth(
|
|
|
1841
1863
|
|
|
1842
1864
|
/** Maximum number of `endpoints[]` entries persisted per runtime (RFC §3.8.2). */
|
|
1843
1865
|
export const RUNTIME_ENDPOINTS_CAP = 32;
|
|
1866
|
+
export const RUNTIME_MODELS_CAP = 128;
|
|
1867
|
+
export const RUNTIME_PARAMETERS_CAP = 64;
|
|
1844
1868
|
|
|
1845
1869
|
/** Injection seam for L2 + L3 endpoint probes — kept testable + side-effect-free. */
|
|
1846
1870
|
export type WsEndpointProbeFn = (args: {
|
|
@@ -2511,20 +2535,34 @@ export async function reloadConfig(ctx: { gateway: Gateway }): Promise<ReloadRes
|
|
|
2511
2535
|
*/
|
|
2512
2536
|
function readAgentRuntimesFromCredentials(
|
|
2513
2537
|
agentIds: string[],
|
|
2514
|
-
): Record<string, { runtime?: string; cwd?: string; openclawGateway?: string; openclawAgent?: string; hermesProfile?: string }> {
|
|
2515
|
-
const out: Record<string, { runtime?: string; cwd?: string; openclawGateway?: string; openclawAgent?: string; hermesProfile?: string }> = {};
|
|
2538
|
+
): Record<string, { runtime?: string; runtimeModel?: string; reasoningEffort?: string; thinking?: boolean; cwd?: string; openclawGateway?: string; openclawAgent?: string; hermesProfile?: string }> {
|
|
2539
|
+
const out: Record<string, { runtime?: string; runtimeModel?: string; reasoningEffort?: string; thinking?: boolean; cwd?: string; openclawGateway?: string; openclawAgent?: string; hermesProfile?: string }> = {};
|
|
2516
2540
|
for (const id of agentIds) {
|
|
2517
2541
|
const file = defaultCredentialsFile(id);
|
|
2518
2542
|
try {
|
|
2519
2543
|
if (!existsSync(file)) continue;
|
|
2520
2544
|
const creds = loadStoredCredentials(file);
|
|
2521
|
-
const entry: { runtime?: string; cwd?: string; openclawGateway?: string; openclawAgent?: string; hermesProfile?: string } = {};
|
|
2545
|
+
const entry: { runtime?: string; runtimeModel?: string; reasoningEffort?: string; thinking?: boolean; cwd?: string; openclawGateway?: string; openclawAgent?: string; hermesProfile?: string } = {};
|
|
2522
2546
|
if (creds.runtime) entry.runtime = creds.runtime;
|
|
2547
|
+
if (creds.runtimeModel) entry.runtimeModel = creds.runtimeModel;
|
|
2548
|
+
if (creds.reasoningEffort) entry.reasoningEffort = creds.reasoningEffort;
|
|
2549
|
+
if (typeof creds.thinking === "boolean") entry.thinking = creds.thinking;
|
|
2523
2550
|
if (creds.cwd) entry.cwd = creds.cwd;
|
|
2524
2551
|
if (creds.openclawGateway) entry.openclawGateway = creds.openclawGateway;
|
|
2525
2552
|
if (creds.openclawAgent) entry.openclawAgent = creds.openclawAgent;
|
|
2526
2553
|
if (creds.hermesProfile) entry.hermesProfile = creds.hermesProfile;
|
|
2527
|
-
if (
|
|
2554
|
+
if (
|
|
2555
|
+
entry.runtime ||
|
|
2556
|
+
entry.runtimeModel ||
|
|
2557
|
+
entry.reasoningEffort ||
|
|
2558
|
+
typeof entry.thinking === "boolean" ||
|
|
2559
|
+
entry.cwd ||
|
|
2560
|
+
entry.openclawGateway ||
|
|
2561
|
+
entry.openclawAgent ||
|
|
2562
|
+
entry.hermesProfile
|
|
2563
|
+
) {
|
|
2564
|
+
out[id] = entry;
|
|
2565
|
+
}
|
|
2528
2566
|
} catch {
|
|
2529
2567
|
// best-effort — skip agents with unreadable credentials
|
|
2530
2568
|
}
|
|
@@ -2769,6 +2807,33 @@ function pickRuntime(params: ProvisionAgentParams): string | undefined {
|
|
|
2769
2807
|
return undefined;
|
|
2770
2808
|
}
|
|
2771
2809
|
|
|
2810
|
+
function pickRuntimeSelection(
|
|
2811
|
+
params: ProvisionAgentParams,
|
|
2812
|
+
): { runtimeModel?: string; reasoningEffort?: string; thinking?: boolean } {
|
|
2813
|
+
const out: { runtimeModel?: string; reasoningEffort?: string; thinking?: boolean } = {};
|
|
2814
|
+
const runtimeModel = pickString(params.runtimeModel, params.credentials?.runtimeModel);
|
|
2815
|
+
const reasoningEffort = pickString(
|
|
2816
|
+
params.reasoningEffort,
|
|
2817
|
+
params.credentials?.reasoningEffort,
|
|
2818
|
+
);
|
|
2819
|
+
if (runtimeModel) out.runtimeModel = runtimeModel;
|
|
2820
|
+
if (reasoningEffort) out.reasoningEffort = reasoningEffort;
|
|
2821
|
+
if (typeof params.thinking === "boolean") {
|
|
2822
|
+
out.thinking = params.thinking;
|
|
2823
|
+
} else if (typeof params.credentials?.thinking === "boolean") {
|
|
2824
|
+
out.thinking = params.credentials.thinking;
|
|
2825
|
+
}
|
|
2826
|
+
return out;
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
function pickString(...values: Array<string | undefined>): string | undefined {
|
|
2830
|
+
for (const value of values) {
|
|
2831
|
+
const trimmed = value?.trim();
|
|
2832
|
+
if (trimmed) return trimmed;
|
|
2833
|
+
}
|
|
2834
|
+
return undefined;
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2772
2837
|
function assertKnownRuntime(runtime: string): void {
|
|
2773
2838
|
const mod = getAdapterModule(runtime);
|
|
2774
2839
|
if (!mod) {
|