@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/README.md +13 -7
- package/dist/resources.d.ts +1 -1
- package/dist/resources.js +20 -1
- package/dist/resources.js.map +1 -1
- package/dist/samplingDriver.d.ts +2 -8
- package/dist/samplingDriver.js +1 -18
- package/dist/samplingDriver.js.map +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.js +58 -9
- package/dist/server.js.map +1 -1
- package/dist/tools.d.ts +273 -11
- package/dist/tools.js +332 -36
- package/dist/tools.js.map +1 -1
- package/package.json +3 -3
package/dist/tools.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
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
|
|
167
|
-
const
|
|
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 &&
|
|
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({
|
|
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:
|
|
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
|
-
|
|
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
|