@deepthonk/mcp 0.1.0 → 0.1.2

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/tools.js CHANGED
@@ -1,9 +1,12 @@
1
- import { readFile } from "node:fs/promises";
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync } from "node:fs";
3
+ import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
2
4
  import { homedir } from "node:os";
5
+ import { dirname, join } from "node:path";
3
6
  import { z } from "zod";
4
7
  import YAML from "yaml";
5
- import { builtInProfiles, claimRunLock, ConfigError, DeepThonkError, getProfile, planBudget, detectResumeState, exportRun, isRunCancelRequested, mutateCandidate, rankCandidates, readRunStatus, requestRunCancel, runDeepThonk, TraceStore } from "@deepthonk/core";
6
- import { createDriver, defaultPricesForProviderConfig, resolveProviderConfig, resolveProviderModels } from "@deepthonk/providers";
8
+ import { builtInProfiles, claimRunLock, ConfigError, DeepThonkError, getProfile, planBudget, detectResumeState, exportRun, isRunCancelRequested, mutateCandidate, rankCandidates, readRunStatus, requestRunCancel, resumeDeepThonk, runConfigSchema, runDeepThonk, TraceStore } from "@deepthonk/core";
9
+ import { createDriver, defaultPricesForProviderConfig, listProfiles, loadDeepThonkEnv, loadNamedProfile, NAMED_PROFILE_NAME_RE, profilePath, profilesDir, rejectAllSecretShapedFields, rejectRawApiKeyFields, resolveProviderConfig, resolveProviderModels, SECRET_KEY_RE, validateNamedProfileBundle } from "@deepthonk/providers";
7
10
  import { recordRunResource } from "./resources.js";
8
11
  export const toolNames = [
9
12
  "deepthonk.plan",
@@ -15,9 +18,15 @@ export const toolNames = [
15
18
  "deepthonk.rank",
16
19
  "deepthonk.mutate",
17
20
  "deepthonk.resume",
18
- "deepthonk.export"
21
+ "deepthonk.export",
22
+ "deepthonk.profile_list",
23
+ "deepthonk.profile_show",
24
+ "deepthonk.profile_save",
25
+ "deepthonk.profile_delete"
19
26
  ];
20
27
  export const planArgsSchema = z.object({
28
+ config_path: z.string().optional().describe("Optional DeepThonk YAML config path."),
29
+ profile_name: z.string().optional().describe("Saved profile bundle name; loads ~/.config/deepthonk/profiles/<name>.yaml. Mutually exclusive with config_path."),
21
30
  profile: z.enum(["quick", "balanced", "paper"]).default("quick"),
22
31
  n: z.number().int().min(2).optional(),
23
32
  k: z.number().int().min(1).optional(),
@@ -28,12 +37,12 @@ const providerNameSchema = z
28
37
  .string()
29
38
  .min(1)
30
39
  .describe("Provider label. Built-ins include fake, deepseek, openrouter, and openai-compatible; custom labels require base_url.")
31
- .refine((provider) => provider !== "sampling", "MCP Sampling is deferred and is not a direct provider option.")
32
40
  .default("fake");
33
41
  export const runArgsSchema = z.object({
34
42
  task: z.string().min(1).describe("Task text to solve. MCP tools do not read task files."),
35
43
  rubric: z.string().optional().describe("Optional judging rubric text."),
36
44
  config_path: z.string().optional().describe("Optional DeepThonk YAML config path, such as ~/.config/deepthonk/config.yaml."),
45
+ profile_name: z.string().optional().describe("Saved profile bundle name; loads ~/.config/deepthonk/profiles/<name>.yaml. Mutually exclusive with config_path."),
37
46
  profile: z.enum(["quick", "balanced", "paper"]).default("quick").describe("Run profile. quick is safest for smoke tests; paper plans 285 calls."),
38
47
  prompt_style: z.enum(["general", "paper-programming"]).optional().describe("Prompt template style. Defaults to paper-programming for the paper profile, general otherwise."),
39
48
  provider: providerNameSchema,
@@ -43,6 +52,10 @@ export const runArgsSchema = z.object({
43
52
  mutator_model: z.string().optional().describe("Model used for critique-guided mutation."),
44
53
  judge_model: z.string().optional().describe("Model used for pairwise judging."),
45
54
  finalizer_model: z.string().optional().describe("Optional model used to polish the winning candidate."),
55
+ sampling_model_hints: z.array(z.string()).optional().describe("Optional MCP Sampling model hints. Hints guide host model choice but do not enforce it."),
56
+ sampling_cost_priority: z.number().min(0).max(1).optional().describe("MCP Sampling model preference for lower cost, from 0 to 1."),
57
+ sampling_speed_priority: z.number().min(0).max(1).optional().describe("MCP Sampling model preference for speed, from 0 to 1."),
58
+ sampling_intelligence_priority: z.number().min(0).max(1).optional().describe("MCP Sampling model preference for intelligence, from 0 to 1."),
46
59
  seed: z.number().int().default(1).describe("Deterministic seed for pair ordering and IDs."),
47
60
  run_dir: z.string().optional().describe("Directory for trace files."),
48
61
  max_calls: z.number().int().optional().describe("Maximum completed provider calls before the run stops at a phase boundary."),
@@ -58,16 +71,18 @@ export const runArgsSchema = z.object({
58
71
  sample_temperature: z.number().min(0).optional().describe("Temperature for initial candidate generation."),
59
72
  mutate_temperature: z.number().min(0).optional().describe("Temperature for critique-guided mutation."),
60
73
  judge_temperature: z.number().min(0).optional().describe("Temperature for pairwise judging."),
74
+ include_prompts: z.boolean().optional().describe("Store rendered prompts in candidate/comparison metadata."),
75
+ include_raw_model_outputs: z.boolean().optional().describe("Store raw provider responses in trace metadata."),
61
76
  concurrency: z.object({
62
77
  generate: z.number().int().min(1).optional(),
63
78
  judge: z.number().int().min(1).optional(),
64
79
  mutate: z.number().int().min(1).optional()
65
80
  }).optional().describe("Per-phase concurrency overrides."),
66
81
  prompts: z.object({
67
- generate: z.object({ system: z.string().optional(), user: z.string().optional() }).optional(),
68
- compare: z.object({ system: z.string().optional(), user: z.string().optional() }).optional(),
69
- mutate: z.object({ system: z.string().optional(), user: z.string().optional() }).optional(),
70
- finalize: z.object({ system: z.string().optional(), user: z.string().optional() }).optional()
82
+ generate: z.object({ system: z.string().optional(), user: z.string().optional() }).describe("Variables: {task}, {rubric}").optional(),
83
+ compare: z.object({ system: z.string().optional(), user: z.string().optional() }).describe("Variables: {task}, {rubric}, {candidateA}, {candidateB}. Output must be strict JSON: {feedback_a, feedback_b, winner: A|B|tie}.").optional(),
84
+ mutate: z.object({ system: z.string().optional(), user: z.string().optional() }).describe("Variables: {task}, {rubric}, {candidate}, {critique}").optional(),
85
+ finalize: z.object({ system: z.string().optional(), user: z.string().optional() }).describe("Variables: {task}, {rubric}, {candidate}").optional()
71
86
  }).optional().describe("Optional per-phase prompt template overrides. Templates use {task}, {rubric}, {candidate}, {candidateA}, {candidateB}, {critique} placeholders. Use {{ and }} to escape literal braces. See docs/customization.md for the variable contract per phase.")
72
87
  });
73
88
  export const jobArgsSchema = z.object({
@@ -76,6 +91,7 @@ export const jobArgsSchema = z.object({
76
91
  });
77
92
  const providerArgsSchema = z.object({
78
93
  config_path: z.string().optional().describe("Optional DeepThonk YAML config path."),
94
+ profile_name: z.string().optional().describe("Saved profile bundle name; loads ~/.config/deepthonk/profiles/<name>.yaml. Mutually exclusive with config_path."),
79
95
  provider: providerNameSchema,
80
96
  base_url: z.string().optional().describe("OpenAI-compatible base URL, ending at /v1."),
81
97
  api_key_env: z.string().optional().describe("Environment variable that contains the API key."),
@@ -87,16 +103,29 @@ const providerArgsSchema = z.object({
87
103
  export const rankArgsSchema = providerArgsSchema.extend({
88
104
  task: z.string().min(1).describe("Task text the candidates answer."),
89
105
  rubric: z.string().optional().describe("Optional judging rubric text."),
90
- candidates: z.array(z.union([z.string(), z.object({ id: z.string().optional(), content: z.string() })])).min(2).describe("Candidate texts or {id, content} objects.")
106
+ candidates: z.array(z.union([z.string(), z.object({ id: z.string().optional(), content: z.string() })])).min(2).describe("Candidate texts or {id, content} objects."),
107
+ judge_temperature: z.number().min(0).optional().describe("Temperature for pairwise judging."),
108
+ lambda: z.number().min(0).optional().describe("Bradley-Terry L2 regularization."),
109
+ concurrency: z.number().int().min(1).optional().describe("Maximum concurrent pairwise comparisons."),
110
+ prompt_style: z.enum(["general", "paper-programming"]).optional(),
111
+ prompts: z.object({
112
+ compare: z.object({ system: z.string().optional(), user: z.string().optional() }).describe("Variables: {task}, {rubric}, {candidateA}, {candidateB}. Output must be strict JSON: {feedback_a, feedback_b, winner: A|B|tie}.").optional()
113
+ }).optional()
91
114
  });
92
115
  export const mutateArgsSchema = providerArgsSchema.extend({
93
116
  task: z.string().min(1).describe("Task text the candidate answers."),
94
117
  rubric: z.string().optional().describe("Optional rubric text."),
95
118
  candidate: z.string().describe("Candidate text to mutate."),
96
- critique: z.string().default("").describe("Critique or guidance for mutation.")
119
+ critique: z.string().default("").describe("Critique or guidance for mutation."),
120
+ mutate_temperature: z.number().min(0).optional().describe("Temperature for mutation."),
121
+ prompt_style: z.enum(["general", "paper-programming"]).optional(),
122
+ prompts: z.object({
123
+ mutate: z.object({ system: z.string().optional(), user: z.string().optional() }).describe("Variables: {task}, {rubric}, {candidate}, {critique}").optional()
124
+ }).optional()
97
125
  });
98
126
  export const resumeArgsSchema = z.object({
99
- run_dir: z.string()
127
+ run_dir: z.string(),
128
+ continue: z.boolean().optional()
100
129
  });
101
130
  export const exportArgsSchema = z.object({
102
131
  run_dir: z.string(),
@@ -151,9 +180,15 @@ export const mutateOutputSchema = z.object({
151
180
  });
152
181
  export function deepthonkPlan(argsInput) {
153
182
  const args = planArgsSchema.parse(argsInput);
154
- if (args.n === undefined && args.k === undefined && args.t === undefined && args.m === undefined) {
183
+ if (args.config_path === undefined && args.profile_name === undefined && args.n === undefined && args.k === undefined && args.t === undefined && args.m === undefined) {
155
184
  return planBudget(args.profile);
156
185
  }
186
+ if (args.config_path !== undefined || args.profile_name !== undefined) {
187
+ throw new ConfigError("deepthonk.plan with config_path or profile_name is async; use deepthonkPlanAsync.", {
188
+ code: "mcp.plan_async_required",
189
+ retryable: false
190
+ });
191
+ }
157
192
  const base = builtInProfiles[args.profile];
158
193
  return planBudget({
159
194
  ...base,
@@ -163,8 +198,25 @@ export function deepthonkPlan(argsInput) {
163
198
  m: args.m ?? base.m
164
199
  });
165
200
  }
166
- export async function deepthonkStart(argsInput) {
167
- const { runConfig, providerConfig } = await resolveMcpRun(argsInput);
201
+ export async function deepthonkPlanAsync(argsInput) {
202
+ const args = planArgsSchema.parse(argsInput);
203
+ const config = await readMcpConfig(args.config_path, args.profile_name);
204
+ const profileName = config.profile ?? args.profile;
205
+ const base = builtInProfiles[profileName];
206
+ return planBudget({
207
+ ...base,
208
+ n: args.n ?? config.algorithm?.n ?? base.n,
209
+ k: args.k ?? config.algorithm?.k ?? base.k,
210
+ t: args.t ?? config.algorithm?.t ?? base.t,
211
+ m: args.m ?? config.algorithm?.m ?? base.m,
212
+ lambda: config.algorithm?.lambda ?? base.lambda,
213
+ sampleTemperature: config.algorithm?.sample_temperature ?? base.sampleTemperature,
214
+ mutateTemperature: config.algorithm?.mutate_temperature ?? base.mutateTemperature,
215
+ judgeTemperature: config.algorithm?.judge_temperature ?? base.judgeTemperature
216
+ });
217
+ }
218
+ export async function deepthonkStart(argsInput, context) {
219
+ const { runConfig, providerConfig } = await resolveMcpRun(argsInput, context);
168
220
  const jobId = `job_${new Date().toISOString().replace(/[:.]/g, "-")}_${Math.random().toString(16).slice(2, 8)}`;
169
221
  const now = new Date().toISOString();
170
222
  const claimed = await claimRunLock(runConfig.runDir, jobId);
@@ -209,7 +261,7 @@ export async function deepthonkStart(argsInput) {
209
261
  .catch(async (error) => {
210
262
  try {
211
263
  const existing = await readRunStatus(runConfig.runDir);
212
- if (existing && existing.state !== "pending")
264
+ if (existing && ["completed", "failed", "cancelled", "budget_exceeded"].includes(existing.state))
213
265
  return;
214
266
  const serialized = serializeToolError(error, runConfig.runDir);
215
267
  await new TraceStore(runConfig.runDir).writeStatus({
@@ -290,8 +342,8 @@ export async function deepthonkCancel(argsInput) {
290
342
  status: (await readRunStatus(runDir)) ?? { state: "cancel_requested", phase: "cancel_requested" }
291
343
  };
292
344
  }
293
- export async function deepthonkRun(argsInput) {
294
- const { runConfig, providerConfig } = await resolveMcpRun(argsInput);
345
+ export async function deepthonkRun(argsInput, context) {
346
+ const { runConfig, providerConfig } = await resolveMcpRun(argsInput, context);
295
347
  const result = await runDeepThonk(runConfig, createDriver(providerConfig), {
296
348
  shouldCancel: () => isRunCancelRequested(runConfig.runDir)
297
349
  });
@@ -305,17 +357,23 @@ export async function deepthonkRun(argsInput) {
305
357
  run_dir: result.runDir
306
358
  };
307
359
  }
308
- async function resolveMcpRun(argsInput) {
360
+ async function resolveMcpRun(argsInput, context) {
309
361
  const raw = objectInput(argsInput);
310
362
  const args = runArgsSchema.parse(argsInput);
311
- const config = await readMcpConfig(args.config_path);
363
+ const config = await readMcpConfig(args.config_path, args.profile_name);
312
364
  const profileName = (raw.profile ?? config.profile ?? args.profile);
313
365
  const baseProfile = getProfile(profileName);
314
366
  const profile = mergeProfileOverrides(baseProfile, config.algorithm, args);
315
- const providerConfig = resolveMcpProviderConfig(args, raw, config);
367
+ const providerConfig = resolveMcpProviderConfig(args, raw, config, context?.createMessage);
368
+ assertSamplingCapability(providerConfig, context);
316
369
  const models = providerConfig.models;
317
370
  const runDir = args.run_dir ?? `runs/mcp-${new Date().toISOString().replace(/[:.]/g, "-")}`;
318
371
  const promptOverrides = mergePromptOverrides(config.prompts, args.prompts);
372
+ const concurrency = capSamplingConcurrency(providerConfig.provider, profile, {
373
+ generate: args.concurrency?.generate ?? config.concurrency?.generate ?? profile.n,
374
+ judge: args.concurrency?.judge ?? config.concurrency?.judge ?? Math.max(1, (profile.n * Math.max(profile.k, profile.m)) / 2),
375
+ mutate: args.concurrency?.mutate ?? config.concurrency?.mutate ?? profile.n - Math.floor(profile.n / 4)
376
+ });
319
377
  return {
320
378
  runConfig: {
321
379
  task: args.task,
@@ -330,11 +388,7 @@ async function resolveMcpRun(argsInput) {
330
388
  mutatorModel: models.mutator,
331
389
  judgeModel: models.judge,
332
390
  finalizerModel: models.finalizer,
333
- concurrency: {
334
- generate: args.concurrency?.generate ?? config.concurrency?.generate ?? profile.n,
335
- judge: args.concurrency?.judge ?? config.concurrency?.judge ?? Math.max(1, (profile.n * Math.max(profile.k, profile.m)) / 2),
336
- mutate: args.concurrency?.mutate ?? config.concurrency?.mutate ?? profile.n - Math.floor(profile.n / 4)
337
- },
391
+ concurrency,
338
392
  retry: {
339
393
  httpRetries: config.retry?.httpRetries ?? 2,
340
394
  invalidJsonRetries: config.retry?.invalidJsonRetries ?? 1,
@@ -347,8 +401,8 @@ async function resolveMcpRun(argsInput) {
347
401
  maxUsd: args.max_usd
348
402
  }, defaultPricesForProviderConfig(providerConfig)),
349
403
  output: {
350
- includeRawModelOutputs: config.output?.includeRawModelOutputs ?? false,
351
- includePrompts: config.output?.includePrompts ?? false
404
+ includeRawModelOutputs: args.include_raw_model_outputs ?? config.output?.includeRawModelOutputs ?? false,
405
+ includePrompts: args.include_prompts ?? config.output?.includePrompts ?? false
352
406
  }
353
407
  },
354
408
  providerConfig
@@ -360,7 +414,19 @@ export async function deepthonkRank(argsInput) {
360
414
  const models = providerConfig.models;
361
415
  const driver = createDriver(providerConfig);
362
416
  const candidates = args.candidates;
363
- const result = await rankCandidates({ task: args.task, rubric: args.rubric, candidates, driver, judgeModel: models.judge, runId: "mcp-rank" });
417
+ const result = await rankCandidates({
418
+ task: args.task,
419
+ rubric: args.rubric,
420
+ candidates,
421
+ driver,
422
+ judgeModel: models.judge,
423
+ runId: "mcp-rank",
424
+ temperature: args.judge_temperature,
425
+ lambda: args.lambda,
426
+ concurrency: args.concurrency,
427
+ promptStyle: args.prompt_style,
428
+ promptOverrides: args.prompts
429
+ });
364
430
  return { scores: result.scores, comparisons: result.comparisons };
365
431
  }
366
432
  export async function deepthonkMutate(argsInput) {
@@ -374,13 +440,44 @@ export async function deepthonkMutate(argsInput) {
374
440
  candidate: { id: "mcp-candidate", content: args.candidate },
375
441
  driver: createDriver(providerConfig),
376
442
  mutatorModel: models.mutator,
377
- temperature: 0.6,
378
- critique: args.critique
443
+ temperature: args.mutate_temperature,
444
+ critique: args.critique,
445
+ promptStyle: args.prompt_style,
446
+ promptOverrides: args.prompts
379
447
  }))
380
448
  };
381
449
  }
382
- export async function deepthonkResume(argsInput) {
450
+ export async function deepthonkResume(argsInput, context) {
383
451
  const args = resumeArgsSchema.parse(argsInput);
452
+ if (args.continue === true) {
453
+ const raw = JSON.parse(await readFile(join(args.run_dir, "config.json"), "utf8"));
454
+ const config = runConfigSchema.parse(raw);
455
+ const providerConfig = resolveProviderConfig({
456
+ provider: config.provider,
457
+ models: {
458
+ generator: config.generatorModel,
459
+ mutator: config.mutatorModel,
460
+ judge: config.judgeModel,
461
+ finalizer: config.finalizerModel
462
+ },
463
+ samplingTransport: config.provider === "sampling" ? context?.createMessage : undefined
464
+ });
465
+ assertSamplingCapability(providerConfig, context);
466
+ const result = await resumeDeepThonk(args.run_dir, createDriver(providerConfig), { provider: config.provider });
467
+ if ("winner" in result) {
468
+ return {
469
+ status: "completed",
470
+ message: "Run resumed and completed.",
471
+ run_id: result.runId,
472
+ run_dir: result.runDir,
473
+ winner_id: result.winner.id,
474
+ final_answer: result.finalAnswer,
475
+ calls: result.calls,
476
+ safe_to_continue: false
477
+ };
478
+ }
479
+ return { ...result };
480
+ }
384
481
  return { ...(await detectResumeState(args.run_dir)) };
385
482
  }
386
483
  export async function deepthonkExport(argsInput) {
@@ -388,12 +485,12 @@ export async function deepthonkExport(argsInput) {
388
485
  return exportRun(args.run_dir, args.format);
389
486
  }
390
487
  async function providerConfigFromArgs(args) {
391
- const config = await readMcpConfig(args.config_path);
488
+ const config = await readMcpConfig(args.config_path, args.profile_name);
392
489
  return resolveMcpProviderConfig(args, objectInput(args), config);
393
490
  }
394
- function resolveMcpProviderConfig(args, raw, config) {
491
+ function resolveMcpProviderConfig(args, raw, config, samplingTransport) {
395
492
  const provider = String(raw.provider ?? config.provider ?? args.provider);
396
- return resolveProviderConfig({
493
+ const resolved = resolveProviderConfig({
397
494
  provider,
398
495
  baseUrl: args.base_url ?? config.base_url,
399
496
  apiKeyEnv: args.api_key_env ?? config.api_key_env,
@@ -411,6 +508,39 @@ function resolveMcpProviderConfig(args, raw, config) {
411
508
  })),
412
509
  retry: { httpRetries: 2, requestTimeoutMs: config.retry?.requestTimeoutMs }
413
510
  });
511
+ if (provider !== "sampling")
512
+ return resolved;
513
+ return {
514
+ ...resolved,
515
+ samplingTransport,
516
+ modelHints: args.sampling_model_hints,
517
+ costPriority: args.sampling_cost_priority,
518
+ speedPriority: args.sampling_speed_priority,
519
+ intelligencePriority: args.sampling_intelligence_priority,
520
+ includeRawOutputs: args.include_raw_model_outputs ?? config.output?.includeRawModelOutputs ?? false
521
+ };
522
+ }
523
+ function assertSamplingCapability(providerConfig, context) {
524
+ if (providerConfig.provider !== "sampling")
525
+ return;
526
+ const capabilities = context?.getClientCapabilities();
527
+ if (!capabilities?.sampling) {
528
+ throw new ConfigError("Connected MCP client does not advertise sampling capability. This client cannot serve as a sampling provider.", {
529
+ code: "provider.sampling_capability_missing",
530
+ retryable: false,
531
+ fix: "Use an MCP client that supports sampling, or choose a direct provider such as deepseek, openrouter, or openai-compatible."
532
+ });
533
+ }
534
+ }
535
+ function capSamplingConcurrency(provider, profile, concurrency) {
536
+ if (provider !== "sampling")
537
+ return concurrency;
538
+ const cap = Math.min(profile.n, 4);
539
+ return {
540
+ generate: Math.min(concurrency.generate, cap),
541
+ judge: Math.min(concurrency.judge, cap),
542
+ mutate: Math.min(concurrency.mutate, cap)
543
+ };
414
544
  }
415
545
  export function toolResult(value) {
416
546
  const structuredContent = typeof value === "object" && value !== null && !Array.isArray(value) ? value : { value };
@@ -444,7 +574,18 @@ function mergeProfileOverrides(base, fileOverrides, args) {
444
574
  judgeTemperature: args.judge_temperature ?? fileOverrides?.judge_temperature ?? base.judgeTemperature
445
575
  };
446
576
  }
447
- async function readMcpConfig(path) {
577
+ async function readMcpConfig(path, profileName) {
578
+ await loadDeepThonkEnv();
579
+ if (profileName) {
580
+ if (path) {
581
+ throw new ConfigError("profile_name and config_path cannot be used together. A named profile replaces the config file.", {
582
+ code: "config.profile_and_config_conflict",
583
+ retryable: false,
584
+ fix: "Choose one: profile_name to load a saved bundle, or config_path to point at a config YAML."
585
+ });
586
+ }
587
+ return (await loadNamedProfile(profileName));
588
+ }
448
589
  if (!path)
449
590
  return {};
450
591
  return YAML.parse(await readFile(resolveMcpPath(path), "utf8"));
@@ -552,4 +693,159 @@ function serializeToolError(error, runDir) {
552
693
  run_dir: runDir
553
694
  };
554
695
  }
696
+ export const profileListArgsSchema = z.object({});
697
+ export const profileNameArgsSchema = z.object({
698
+ name: z.string().min(1)
699
+ });
700
+ export const profileSaveArgsSchema = z.object({
701
+ name: z.string().min(1),
702
+ force: z.boolean().optional(),
703
+ profile: z.enum(["quick", "balanced", "paper"]).optional(),
704
+ prompt_style: z.enum(["general", "paper-programming"]).optional(),
705
+ provider: z.string().min(1).optional(),
706
+ base_url: z.string().optional(),
707
+ api_key_env: z.string().optional(),
708
+ models: z.object({
709
+ generator: z.string().optional(),
710
+ mutator: z.string().optional(),
711
+ judge: z.string().optional(),
712
+ finalizer: z.string().optional()
713
+ }).optional(),
714
+ providers: z.record(z.unknown()).optional(),
715
+ algorithm: z.record(z.unknown()).optional(),
716
+ prompts: z.record(z.object({ system: z.string().optional(), user: z.string().optional() })).optional(),
717
+ budget: z.unknown().optional(),
718
+ concurrency: z.unknown().optional(),
719
+ retry: z.unknown().optional(),
720
+ output: z.unknown().optional()
721
+ }).strict();
722
+ export const profileListOutputSchema = z.object({
723
+ profiles: z.array(z.string())
724
+ });
725
+ export const profileShowOutputSchema = z.object({
726
+ profile: z.object({}).passthrough()
727
+ });
728
+ export const profileSaveOutputSchema = z.object({
729
+ path: z.string()
730
+ });
731
+ export const profileDeleteOutputSchema = z.object({
732
+ deleted: z.string()
733
+ });
734
+ export async function deepthonkProfileList(_argsInput) {
735
+ profileListArgsSchema.parse(_argsInput ?? {});
736
+ return { profiles: await listProfiles() };
737
+ }
738
+ export async function deepthonkProfileShow(argsInput) {
739
+ const args = profileNameArgsSchema.parse(argsInput);
740
+ return { profile: redactedProfile(await loadNamedProfile(args.name)) };
741
+ }
742
+ export async function deepthonkProfileSave(argsInput) {
743
+ rejectRawApiKeyFields(argsInput, "profile_save");
744
+ rejectAllSecretShapedFields(argsInput, "profile_save");
745
+ const args = parseProfileSaveArgs(argsInput);
746
+ const { name, force, ...profile } = args;
747
+ const path = await saveMcpProfileBundle(name, stripUndefined(profile), Boolean(force));
748
+ return { path };
749
+ }
750
+ export async function deepthonkProfileDelete(argsInput) {
751
+ const args = profileNameArgsSchema.parse(argsInput);
752
+ await loadNamedProfile(args.name);
753
+ const path = profilePath(args.name);
754
+ await unlink(path);
755
+ return { deleted: path };
756
+ }
757
+ function parseProfileSaveArgs(argsInput) {
758
+ try {
759
+ return profileSaveArgsSchema.parse(argsInput);
760
+ }
761
+ catch (error) {
762
+ if (error instanceof z.ZodError) {
763
+ throw new ConfigError(formatZodIssues(error), {
764
+ code: "mcp.invalid_arguments",
765
+ retryable: false,
766
+ fix: "Fix the tool arguments and retry."
767
+ });
768
+ }
769
+ throw error;
770
+ }
771
+ }
772
+ function formatZodIssues(error) {
773
+ return error.issues.map((issue) => (issue.path.length > 0 ? `${issue.path.join(".")}: ${issue.message}` : issue.message)).join("; ");
774
+ }
775
+ function redactedProfile(value) {
776
+ const serialized = JSON.stringify(value, (key, inner) => {
777
+ if (SECRET_KEY_RE.test(key) && inner)
778
+ return "[redacted]";
779
+ return inner;
780
+ });
781
+ return serialized === undefined ? undefined : JSON.parse(serialized);
782
+ }
783
+ async function saveMcpProfileBundle(name, bundle, force) {
784
+ assertMcpProfileName(name);
785
+ rejectRawApiKeyFields(bundle, name);
786
+ rejectAllSecretShapedFields(bundle, name);
787
+ validateNamedProfileBundle(bundle, name);
788
+ const dir = profilesDir();
789
+ const target = profilePath(name);
790
+ await mkdir(dir, { recursive: true });
791
+ if (existsSync(target) && !force) {
792
+ throw new ConfigError(`Named profile '${name}' already exists at ${target}.`, {
793
+ code: "config.profile_exists",
794
+ retryable: false,
795
+ fix: "Pass force: true to overwrite it."
796
+ });
797
+ }
798
+ const yaml = YAML.stringify(bundle);
799
+ if (force) {
800
+ await writeMcpProfileOverwrite(target, yaml, name);
801
+ }
802
+ else {
803
+ await writeMcpProfileCreate(target, yaml, name);
804
+ }
805
+ return target;
806
+ }
807
+ function assertMcpProfileName(name) {
808
+ if (!NAMED_PROFILE_NAME_RE.test(name)) {
809
+ throw new ConfigError(`Invalid profile name '${name}'. Names must start with a letter and contain only letters, digits, hyphens, and underscores (max 64 chars).`, {
810
+ code: "config.profile_invalid_name",
811
+ retryable: false,
812
+ fix: "Rename the profile to match /^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/."
813
+ });
814
+ }
815
+ }
816
+ async function writeMcpProfileCreate(target, yaml, name) {
817
+ try {
818
+ await writeFile(target, yaml, { encoding: "utf8", flag: "wx" });
819
+ }
820
+ catch (error) {
821
+ if (error.code === "EEXIST") {
822
+ throw new ConfigError(`Named profile '${name}' already exists at ${target}.`, {
823
+ code: "config.profile_exists",
824
+ retryable: false,
825
+ fix: "Pass force: true to overwrite it."
826
+ });
827
+ }
828
+ throw error;
829
+ }
830
+ }
831
+ async function writeMcpProfileOverwrite(target, yaml, name) {
832
+ const tempPath = join(dirname(target), `.${name}.${randomUUID()}.tmp`);
833
+ await writeFile(tempPath, yaml, { encoding: "utf8", flag: "wx" });
834
+ try {
835
+ await rename(tempPath, target);
836
+ }
837
+ catch (error) {
838
+ await unlink(tempPath).catch(() => undefined);
839
+ throw error;
840
+ }
841
+ }
842
+ function stripUndefined(value) {
843
+ if (Array.isArray(value))
844
+ return value.map(stripUndefined);
845
+ if (!value || typeof value !== "object")
846
+ return value;
847
+ return Object.fromEntries(Object.entries(value)
848
+ .filter(([, inner]) => inner !== undefined)
849
+ .map(([key, inner]) => [key, stripUndefined(inner)]));
850
+ }
555
851
  //# sourceMappingURL=tools.js.map