@botcord/daemon 0.2.77 → 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/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 +1 -1
- 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__/deepseek-tui-adapter.test.ts +30 -1
- 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
|
@@ -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 {
|
|
@@ -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) {
|