@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.
@@ -106,7 +106,12 @@ async function startMockDeepseekServer(opts?: {
106
106
  };
107
107
  }
108
108
 
109
- function runAdapter(serverUrl: string, authToken: string, sessionId: string | null = null) {
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 (entry.runtime || entry.cwd || entry.openclawGateway || entry.openclawAgent || entry.hermesProfile) out[id] = entry;
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) {