@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
|
@@ -22,6 +22,12 @@ export interface DiscoveredAgentCredential {
|
|
|
22
22
|
* in that case.
|
|
23
23
|
*/
|
|
24
24
|
runtime?: string;
|
|
25
|
+
/** Runtime model id/alias selected for this agent. */
|
|
26
|
+
runtimeModel?: string;
|
|
27
|
+
/** Runtime reasoning effort selected for this agent. */
|
|
28
|
+
reasoningEffort?: string;
|
|
29
|
+
/** Kimi-style thinking toggle selected for this agent. */
|
|
30
|
+
thinking?: boolean;
|
|
25
31
|
/** Working directory cached alongside `runtime`. */
|
|
26
32
|
cwd?: string;
|
|
27
33
|
/** OpenClaw gateway profile name from credentials (only meaningful for openclaw-acp). */
|
package/dist/agent-discovery.js
CHANGED
|
@@ -96,6 +96,12 @@ export function discoverAgentCredentials(opts = {}) {
|
|
|
96
96
|
entry.displayName = creds.displayName;
|
|
97
97
|
if (creds.runtime)
|
|
98
98
|
entry.runtime = creds.runtime;
|
|
99
|
+
if (creds.runtimeModel)
|
|
100
|
+
entry.runtimeModel = creds.runtimeModel;
|
|
101
|
+
if (creds.reasoningEffort)
|
|
102
|
+
entry.reasoningEffort = creds.reasoningEffort;
|
|
103
|
+
if (typeof creds.thinking === "boolean")
|
|
104
|
+
entry.thinking = creds.thinking;
|
|
99
105
|
if (creds.cwd)
|
|
100
106
|
entry.cwd = creds.cwd;
|
|
101
107
|
if (creds.openclawGateway)
|
|
@@ -3,6 +3,12 @@ import type { DaemonConfig, OpenclawGatewayProfile } from "./config.js";
|
|
|
3
3
|
/** Per-agent metadata cached from credentials, used by `buildManagedRoutes`. */
|
|
4
4
|
export interface AgentRuntimeMeta {
|
|
5
5
|
runtime?: string;
|
|
6
|
+
/** Runtime model id/alias selected for this agent. */
|
|
7
|
+
runtimeModel?: string;
|
|
8
|
+
/** Runtime reasoning effort selected for this agent. */
|
|
9
|
+
reasoningEffort?: string;
|
|
10
|
+
/** Kimi-style thinking toggle selected for this agent. */
|
|
11
|
+
thinking?: boolean;
|
|
6
12
|
cwd?: string;
|
|
7
13
|
/** OpenClaw gateway profile name to lookup in the registry. */
|
|
8
14
|
openclawGateway?: string;
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { resolveAgentIds } from "./config.js";
|
|
5
5
|
import { agentWorkspaceDir } from "./agent-workspace.js";
|
|
6
6
|
import { log as daemonLog } from "./log.js";
|
|
7
|
+
import { buildRuntimeSelectionExtraArgs, mergeRuntimeExtraArgs, } from "./runtime-route-options.js";
|
|
7
8
|
function expandHome(p) {
|
|
8
9
|
if (p === "~")
|
|
9
10
|
return homedir();
|
|
@@ -259,10 +260,10 @@ export function buildManagedRoutes(agentIds, agentRuntimes, defaultRoute, opencl
|
|
|
259
260
|
match: { accountId: agentId },
|
|
260
261
|
runtime,
|
|
261
262
|
cwd: meta.cwd || agentWorkspaceDir(agentId),
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
263
|
+
...(() => {
|
|
264
|
+
const extraArgs = mergeRuntimeExtraArgs(defaultRoute.extraArgs, buildRuntimeSelectionExtraArgs(runtime, meta));
|
|
265
|
+
return extraArgs ? { extraArgs } : {};
|
|
266
|
+
})(),
|
|
266
267
|
};
|
|
267
268
|
if (runtime === "openclaw-acp") {
|
|
268
269
|
// Per RFC §3.4: prefer credentials, fall back to defaultRoute.gateway.
|
package/dist/daemon.d.ts
CHANGED
|
@@ -121,6 +121,9 @@ export interface BootBackfillResult {
|
|
|
121
121
|
credentialPathByAgentId: Map<string, string>;
|
|
122
122
|
agentRuntimes: Record<string, {
|
|
123
123
|
runtime?: string;
|
|
124
|
+
runtimeModel?: string;
|
|
125
|
+
reasoningEffort?: string;
|
|
126
|
+
thinking?: boolean;
|
|
124
127
|
cwd?: string;
|
|
125
128
|
openclawGateway?: string;
|
|
126
129
|
openclawAgent?: string;
|
package/dist/daemon.js
CHANGED
|
@@ -527,9 +527,19 @@ export function backfillBootAgents(agents, opts) {
|
|
|
527
527
|
for (const a of agents) {
|
|
528
528
|
if (a.credentialsFile)
|
|
529
529
|
credentialPathByAgentId.set(a.agentId, a.credentialsFile);
|
|
530
|
-
if (a.runtime ||
|
|
530
|
+
if (a.runtime ||
|
|
531
|
+
a.runtimeModel ||
|
|
532
|
+
a.reasoningEffort ||
|
|
533
|
+
typeof a.thinking === "boolean" ||
|
|
534
|
+
a.cwd ||
|
|
535
|
+
a.openclawGateway ||
|
|
536
|
+
a.openclawAgent ||
|
|
537
|
+
a.hermesProfile) {
|
|
531
538
|
agentRuntimes[a.agentId] = {
|
|
532
539
|
...(a.runtime ? { runtime: a.runtime } : {}),
|
|
540
|
+
...(a.runtimeModel ? { runtimeModel: a.runtimeModel } : {}),
|
|
541
|
+
...(a.reasoningEffort ? { reasoningEffort: a.reasoningEffort } : {}),
|
|
542
|
+
...(typeof a.thinking === "boolean" ? { thinking: a.thinking } : {}),
|
|
533
543
|
...(a.cwd ? { cwd: a.cwd } : {}),
|
|
534
544
|
...(a.openclawGateway ? { openclawGateway: a.openclawGateway } : {}),
|
|
535
545
|
...(a.openclawAgent ? { openclawAgent: a.openclawAgent } : {}),
|
|
@@ -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 "";
|
|
@@ -202,6 +202,11 @@ export class DeepseekTuiAdapter {
|
|
|
202
202
|
auto_approve: opts.trustLevel !== "public",
|
|
203
203
|
archived: false,
|
|
204
204
|
};
|
|
205
|
+
const selection = parseDeepseekRuntimeSelection(opts.extraArgs);
|
|
206
|
+
if (selection.model)
|
|
207
|
+
body.model = selection.model;
|
|
208
|
+
if (selection.reasoningEffort)
|
|
209
|
+
body.reasoning_effort = selection.reasoningEffort;
|
|
205
210
|
if (opts.systemContext)
|
|
206
211
|
body.system_prompt = opts.systemContext;
|
|
207
212
|
const res = await this.requestJson(`${baseUrl}/v1/threads`, {
|
|
@@ -236,16 +241,22 @@ export class DeepseekTuiAdapter {
|
|
|
236
241
|
});
|
|
237
242
|
let turnId = "";
|
|
238
243
|
try {
|
|
244
|
+
const selection = parseDeepseekRuntimeSelection(opts.extraArgs);
|
|
245
|
+
const body = {
|
|
246
|
+
prompt: opts.text,
|
|
247
|
+
mode: "agent",
|
|
248
|
+
allow_shell: opts.trustLevel !== "public",
|
|
249
|
+
trust_mode: opts.trustLevel !== "public",
|
|
250
|
+
auto_approve: opts.trustLevel !== "public",
|
|
251
|
+
};
|
|
252
|
+
if (selection.model)
|
|
253
|
+
body.model = selection.model;
|
|
254
|
+
if (selection.reasoningEffort)
|
|
255
|
+
body.reasoning_effort = selection.reasoningEffort;
|
|
239
256
|
const started = await this.requestJson(`${baseUrl}/v1/threads/${encodeURIComponent(threadId)}/turns`, {
|
|
240
257
|
method: "POST",
|
|
241
258
|
headers,
|
|
242
|
-
body: JSON.stringify(
|
|
243
|
-
prompt: opts.text,
|
|
244
|
-
mode: "agent",
|
|
245
|
-
allow_shell: opts.trustLevel !== "public",
|
|
246
|
-
trust_mode: opts.trustLevel !== "public",
|
|
247
|
-
auto_approve: opts.trustLevel !== "public",
|
|
248
|
-
}),
|
|
259
|
+
body: JSON.stringify(body),
|
|
249
260
|
signal,
|
|
250
261
|
});
|
|
251
262
|
turnId = stringField(started?.turn, "id") ?? stringField(started, "turn_id") ?? "";
|
|
@@ -457,6 +468,43 @@ function parseSseFrame(raw) {
|
|
|
457
468
|
function authHeaders(token) {
|
|
458
469
|
return token ? { authorization: `Bearer ${token}` } : {};
|
|
459
470
|
}
|
|
471
|
+
function parseDeepseekRuntimeSelection(extraArgs) {
|
|
472
|
+
const out = {};
|
|
473
|
+
if (!extraArgs?.length)
|
|
474
|
+
return out;
|
|
475
|
+
for (let i = 0; i < extraArgs.length; i += 1) {
|
|
476
|
+
const arg = extraArgs[i];
|
|
477
|
+
if (arg === "--model") {
|
|
478
|
+
const value = nextArgValue(extraArgs, i);
|
|
479
|
+
if (value !== undefined) {
|
|
480
|
+
out.model = value;
|
|
481
|
+
i += 1;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
else if (arg.startsWith("--model=")) {
|
|
485
|
+
out.model = arg.slice("--model=".length);
|
|
486
|
+
}
|
|
487
|
+
else if (arg === "--reasoning-effort") {
|
|
488
|
+
const value = nextArgValue(extraArgs, i);
|
|
489
|
+
if (value !== undefined) {
|
|
490
|
+
out.reasoningEffort = value;
|
|
491
|
+
i += 1;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
else if (arg.startsWith("--reasoning-effort=")) {
|
|
495
|
+
out.reasoningEffort = arg.slice("--reasoning-effort=".length);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return out;
|
|
499
|
+
}
|
|
500
|
+
function nextArgValue(args, index) {
|
|
501
|
+
const next = args[index + 1];
|
|
502
|
+
if (typeof next !== "string")
|
|
503
|
+
return undefined;
|
|
504
|
+
if (!next.startsWith("-"))
|
|
505
|
+
return next;
|
|
506
|
+
return /^-\d/.test(next) ? next : undefined;
|
|
507
|
+
}
|
|
460
508
|
function poolKey(opts) {
|
|
461
509
|
return opts.accountId || "default";
|
|
462
510
|
}
|
package/dist/provision.d.ts
CHANGED
|
@@ -107,6 +107,8 @@ export declare function collectRuntimeSnapshot(opts?: {
|
|
|
107
107
|
export declare function attachRuntimeHealth(snapshot: ListRuntimesResult, live: GatewayRuntimeSnapshot): ListRuntimesResult;
|
|
108
108
|
/** Maximum number of `endpoints[]` entries persisted per runtime (RFC §3.8.2). */
|
|
109
109
|
export declare const RUNTIME_ENDPOINTS_CAP = 32;
|
|
110
|
+
export declare const RUNTIME_MODELS_CAP = 128;
|
|
111
|
+
export declare const RUNTIME_PARAMETERS_CAP = 64;
|
|
110
112
|
/** Injection seam for L2 + L3 endpoint probes — kept testable + side-effect-free. */
|
|
111
113
|
export type WsEndpointProbeFn = (args: {
|
|
112
114
|
url: string;
|
package/dist/provision.js
CHANGED
|
@@ -18,6 +18,8 @@ import { hermesProfileHomeDir, isValidHermesProfileName, listHermesProfiles, } f
|
|
|
18
18
|
import { log as daemonLog } from "./log.js";
|
|
19
19
|
import { discoverAgentCredentials } from "./agent-discovery.js";
|
|
20
20
|
import { resolveMemoryDir } from "./working-memory.js";
|
|
21
|
+
import { discoverRuntimeModelCatalog } from "./runtime-models.js";
|
|
22
|
+
import { buildRuntimeSelectionExtraArgs, mergeRuntimeExtraArgs, } from "./runtime-route-options.js";
|
|
21
23
|
/**
|
|
22
24
|
* Build a dispatcher function that routes a `ControlFrame` to the right
|
|
23
25
|
* handler. Returned function signature matches
|
|
@@ -783,6 +785,9 @@ function upsertManagedRouteForCredentials(credentials, cfg, gateway) {
|
|
|
783
785
|
runtime: credentials.runtime ?? cfg.defaultRoute.adapter,
|
|
784
786
|
cwd: credentials.cwd ?? agentWorkspaceDir(credentials.agentId),
|
|
785
787
|
};
|
|
788
|
+
const extraArgs = mergeRuntimeExtraArgs(cfg.defaultRoute.extraArgs, buildRuntimeSelectionExtraArgs(synthRoute.runtime, credentials));
|
|
789
|
+
if (extraArgs)
|
|
790
|
+
synthRoute.extraArgs = extraArgs;
|
|
786
791
|
if (synthRoute.runtime === "openclaw-acp") {
|
|
787
792
|
const profile = (cfg.openclawGateways ?? []).find((g) => g.name === credentials.openclawGateway);
|
|
788
793
|
if (profile) {
|
|
@@ -887,6 +892,13 @@ async function materializeCredentials(params, cfg, ctx, explicitCwd) {
|
|
|
887
892
|
record.tokenExpiresAt = c.tokenExpiresAt;
|
|
888
893
|
if (runtime)
|
|
889
894
|
record.runtime = runtime;
|
|
895
|
+
const runtimeSelection = pickRuntimeSelection(params);
|
|
896
|
+
if (runtimeSelection.runtimeModel)
|
|
897
|
+
record.runtimeModel = runtimeSelection.runtimeModel;
|
|
898
|
+
if (runtimeSelection.reasoningEffort)
|
|
899
|
+
record.reasoningEffort = runtimeSelection.reasoningEffort;
|
|
900
|
+
if (typeof runtimeSelection.thinking === "boolean")
|
|
901
|
+
record.thinking = runtimeSelection.thinking;
|
|
890
902
|
record.cwd = cwd;
|
|
891
903
|
const openclawSel = pickOpenclawSelection(params);
|
|
892
904
|
if (openclawSel.gateway)
|
|
@@ -922,6 +934,13 @@ async function materializeCredentials(params, cfg, ctx, explicitCwd) {
|
|
|
922
934
|
};
|
|
923
935
|
if (runtime)
|
|
924
936
|
record.runtime = runtime;
|
|
937
|
+
const runtimeSelection = pickRuntimeSelection(params);
|
|
938
|
+
if (runtimeSelection.runtimeModel)
|
|
939
|
+
record.runtimeModel = runtimeSelection.runtimeModel;
|
|
940
|
+
if (runtimeSelection.reasoningEffort)
|
|
941
|
+
record.reasoningEffort = runtimeSelection.reasoningEffort;
|
|
942
|
+
if (typeof runtimeSelection.thinking === "boolean")
|
|
943
|
+
record.thinking = runtimeSelection.thinking;
|
|
925
944
|
record.cwd = cwd;
|
|
926
945
|
const openclawSel = pickOpenclawSelection(params);
|
|
927
946
|
if (openclawSel.gateway)
|
|
@@ -1449,6 +1468,12 @@ export function collectRuntimeSnapshot(opts = {}) {
|
|
|
1449
1468
|
record.version = entry.result.version;
|
|
1450
1469
|
if (entry.result.path)
|
|
1451
1470
|
record.path = entry.result.path;
|
|
1471
|
+
const catalog = discoverRuntimeModelCatalog(entry);
|
|
1472
|
+
const models = catalog.models;
|
|
1473
|
+
if (models?.length)
|
|
1474
|
+
record.models = models.slice(0, RUNTIME_MODELS_CAP);
|
|
1475
|
+
if (catalog.parameters?.length)
|
|
1476
|
+
record.parameters = catalog.parameters.slice(0, RUNTIME_PARAMETERS_CAP);
|
|
1452
1477
|
// Gateway's probe surface doesn't expose an `error` string today — it
|
|
1453
1478
|
// already swallows throws into `{available: false}`. We leave the wire
|
|
1454
1479
|
// field blank in that case and let callers treat `!available` as reason
|
|
@@ -1502,6 +1527,8 @@ export function attachRuntimeHealth(snapshot, live) {
|
|
|
1502
1527
|
}
|
|
1503
1528
|
/** Maximum number of `endpoints[]` entries persisted per runtime (RFC §3.8.2). */
|
|
1504
1529
|
export const RUNTIME_ENDPOINTS_CAP = 32;
|
|
1530
|
+
export const RUNTIME_MODELS_CAP = 128;
|
|
1531
|
+
export const RUNTIME_PARAMETERS_CAP = 64;
|
|
1505
1532
|
export function classifyOpenclawAuthError(message) {
|
|
1506
1533
|
const text = (message ?? "").toLowerCase();
|
|
1507
1534
|
if (!text)
|
|
@@ -2096,6 +2123,12 @@ function readAgentRuntimesFromCredentials(agentIds) {
|
|
|
2096
2123
|
const entry = {};
|
|
2097
2124
|
if (creds.runtime)
|
|
2098
2125
|
entry.runtime = creds.runtime;
|
|
2126
|
+
if (creds.runtimeModel)
|
|
2127
|
+
entry.runtimeModel = creds.runtimeModel;
|
|
2128
|
+
if (creds.reasoningEffort)
|
|
2129
|
+
entry.reasoningEffort = creds.reasoningEffort;
|
|
2130
|
+
if (typeof creds.thinking === "boolean")
|
|
2131
|
+
entry.thinking = creds.thinking;
|
|
2099
2132
|
if (creds.cwd)
|
|
2100
2133
|
entry.cwd = creds.cwd;
|
|
2101
2134
|
if (creds.openclawGateway)
|
|
@@ -2104,8 +2137,16 @@ function readAgentRuntimesFromCredentials(agentIds) {
|
|
|
2104
2137
|
entry.openclawAgent = creds.openclawAgent;
|
|
2105
2138
|
if (creds.hermesProfile)
|
|
2106
2139
|
entry.hermesProfile = creds.hermesProfile;
|
|
2107
|
-
if (entry.runtime ||
|
|
2140
|
+
if (entry.runtime ||
|
|
2141
|
+
entry.runtimeModel ||
|
|
2142
|
+
entry.reasoningEffort ||
|
|
2143
|
+
typeof entry.thinking === "boolean" ||
|
|
2144
|
+
entry.cwd ||
|
|
2145
|
+
entry.openclawGateway ||
|
|
2146
|
+
entry.openclawAgent ||
|
|
2147
|
+
entry.hermesProfile) {
|
|
2108
2148
|
out[id] = entry;
|
|
2149
|
+
}
|
|
2109
2150
|
}
|
|
2110
2151
|
catch {
|
|
2111
2152
|
// best-effort — skip agents with unreadable credentials
|
|
@@ -2309,6 +2350,30 @@ function pickRuntime(params) {
|
|
|
2309
2350
|
}
|
|
2310
2351
|
return undefined;
|
|
2311
2352
|
}
|
|
2353
|
+
function pickRuntimeSelection(params) {
|
|
2354
|
+
const out = {};
|
|
2355
|
+
const runtimeModel = pickString(params.runtimeModel, params.credentials?.runtimeModel);
|
|
2356
|
+
const reasoningEffort = pickString(params.reasoningEffort, params.credentials?.reasoningEffort);
|
|
2357
|
+
if (runtimeModel)
|
|
2358
|
+
out.runtimeModel = runtimeModel;
|
|
2359
|
+
if (reasoningEffort)
|
|
2360
|
+
out.reasoningEffort = reasoningEffort;
|
|
2361
|
+
if (typeof params.thinking === "boolean") {
|
|
2362
|
+
out.thinking = params.thinking;
|
|
2363
|
+
}
|
|
2364
|
+
else if (typeof params.credentials?.thinking === "boolean") {
|
|
2365
|
+
out.thinking = params.credentials.thinking;
|
|
2366
|
+
}
|
|
2367
|
+
return out;
|
|
2368
|
+
}
|
|
2369
|
+
function pickString(...values) {
|
|
2370
|
+
for (const value of values) {
|
|
2371
|
+
const trimmed = value?.trim();
|
|
2372
|
+
if (trimmed)
|
|
2373
|
+
return trimmed;
|
|
2374
|
+
}
|
|
2375
|
+
return undefined;
|
|
2376
|
+
}
|
|
2312
2377
|
function assertKnownRuntime(runtime) {
|
|
2313
2378
|
const mod = getAdapterModule(runtime);
|
|
2314
2379
|
if (!mod) {
|