@deepthonk/providers 0.1.4 → 0.2.0
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 +26 -11
- package/dist/defaults.d.ts +10 -0
- package/dist/defaults.js +18 -7
- package/dist/defaults.js.map +1 -1
- package/dist/externalConfig.d.ts +67 -0
- package/dist/externalConfig.js +318 -0
- package/dist/externalConfig.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/openaiCompatible.d.ts +5 -1
- package/dist/openaiCompatible.js +263 -89
- package/dist/openaiCompatible.js.map +1 -1
- package/dist/profileRegistry.js +4 -2
- package/dist/profileRegistry.js.map +1 -1
- package/dist/providerReplay.d.ts +282 -0
- package/dist/providerReplay.js +186 -0
- package/dist/providerReplay.js.map +1 -0
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +42 -3
- package/dist/registry.js.map +1 -1
- package/dist/routeLimiter.d.ts +36 -0
- package/dist/routeLimiter.js +164 -0
- package/dist/routeLimiter.js.map +1 -0
- package/dist/sampling.d.ts +2 -1
- package/dist/sampling.js +136 -70
- package/dist/sampling.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ deepthonk plan --profile paper
|
|
|
31
31
|
|
|
32
32
|
The paper profile plans 285 model calls and 8 sequential rounds. Confirm budget and provider pricing before pointing it at paid models. The short alias `dt` is installed alongside `deepthonk`. Develop from source: see [Development](#development).
|
|
33
33
|
|
|
34
|
-
>
|
|
34
|
+
> DeepThonk is an independent TypeScript reimplementation and practical integration layer; the published algorithm is the load-bearing part. Cost guarantees rely on provider pricing being present in config. Trace-v2 resume can reuse validated per-item receipts after a crash, while older traces replay an incomplete phase wholesale. The MCP HTTP transport remains loopback-only. The included acceptance smoke uses the deterministic fake provider and a toy task — it is not a CF-73 / HLE reproduction; see `docs/` and [Acknowledgments](#acknowledgments) for the canonical paper and Python reference.
|
|
35
35
|
|
|
36
36
|
## Setup
|
|
37
37
|
|
|
@@ -70,7 +70,7 @@ deepthonk plan --config ~/.config/deepthonk/config.yaml
|
|
|
70
70
|
deepthonk run --task task.md --config ~/.config/deepthonk/config.yaml --profile quick --dry-run
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
Start with `--profile quick` and consider `--max-concurrency`, `--max-input-tokens`, `--max-output-tokens`, `--max-usd`, and `--request-timeout-ms` before larger paid profiles.
|
|
73
|
+
Start with `--profile quick` and consider `--max-concurrency`, `--max-calls`, `--max-input-tokens`, `--max-output-tokens`, `--max-usd`, and `--request-timeout-ms` before larger paid profiles. `max_calls` is reserved before dispatch and counts logical model invocations, including failed calls and invalid-JSON retries; provider-internal HTTP retries are reported separately. Token/USD totals are known only after responses and can overshoot by at most the active concurrency window. Plans keep nominal `calls` separate from finalizer and retry headroom in `worst_case_calls`.
|
|
74
74
|
|
|
75
75
|
## OpenAI-Compatible Providers
|
|
76
76
|
|
|
@@ -86,7 +86,7 @@ deepthonk run \
|
|
|
86
86
|
--judge-model strong-model
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
The driver calls `POST {base_url}/chat/completions` and requests JSON mode for comparisons when supported. If a provider returns
|
|
89
|
+
The driver calls `POST {base_url}/chat/completions` and requests JSON mode for comparisons when supported. If a provider returns `400` or `422` with a body that mentions `response_format` or `json`, the driver retries without JSON mode, coordinates that capability probe across concurrent calls, and remembers the result for the process. Set `supports_json_mode: false` in YAML to disable JSON mode up front.
|
|
90
90
|
|
|
91
91
|
Provider names are flexible. For a custom OpenAI-compatible endpoint, use any provider label with `--base-url`, `--api-key-env`, and role-specific model flags. For OpenRouter:
|
|
92
92
|
|
|
@@ -111,7 +111,7 @@ The MCP server exposes the same engine the CLI runs. Once wired into an MCP host
|
|
|
111
111
|
|
|
112
112
|
Provider API keys come from the host process's environment, not from DeepThonk's config alone. Each host handles env passthrough slightly differently — see the concrete patterns below.
|
|
113
113
|
|
|
114
|
-
MCP Sampling is supported as `provider: "sampling"` when the connected host advertises the MCP sampling capability. Sampling
|
|
114
|
+
MCP Sampling is supported as `provider: "sampling"` when the connected host advertises the MCP sampling capability. Blocking `run`, `rank`, and `mutate` work over stdio and stateful Streamable HTTP. HTTP background `start` rejects Sampling because nested Sampling must remain attached to the initiating request; stdio background runs and direct-provider HTTP background runs remain available. Sampling is not available from standalone CLI runs.
|
|
115
115
|
|
|
116
116
|
### Claude Code
|
|
117
117
|
|
|
@@ -174,10 +174,11 @@ with the relevant provider env vars set on the process. If you'd rather install
|
|
|
174
174
|
For local web hosts, or when stdio isn't available:
|
|
175
175
|
|
|
176
176
|
```bash
|
|
177
|
-
deepthonk serve-mcp --transport http --port 3333
|
|
177
|
+
deepthonk serve-mcp --transport http --port 3333 \
|
|
178
|
+
--max-active-jobs 2 --max-queued-jobs 32
|
|
178
179
|
```
|
|
179
180
|
|
|
180
|
-
The server binds `127.0.0.1:3333` only
|
|
181
|
+
The server binds `127.0.0.1:3333` only and exposes stateful `POST`, `GET`, and `DELETE` at `http://127.0.0.1:3333/mcp`. Sessions use cryptographic IDs, expire after 30 idle minutes when no request is active, and are capped at 64. DNS rebinding protection is on (CVE-2025-66414): requests with `Host` headers outside `127.0.0.1:3333` / `localhost:3333` are rejected. The wrapper also rejects non-JSON POSTs, non-loopback `Origin` headers, and `Sec-Fetch-Site: cross-site` before reading the body. It has no bearer auth. Do not expose this port through a reverse proxy without re-evaluating that trust boundary.
|
|
181
182
|
|
|
182
183
|
### Tools
|
|
183
184
|
|
|
@@ -188,6 +189,8 @@ The server binds `127.0.0.1:3333` only (loopback) and the endpoint is `POST http
|
|
|
188
189
|
| `deepthonk.status` | Poll job status from a `run_dir`. |
|
|
189
190
|
| `deepthonk.result` | Return final summary + winner once a job is complete. |
|
|
190
191
|
| `deepthonk.cancel` | Request cancellation by writing `cancel.json` into the run directory. |
|
|
192
|
+
| `deepthonk.lock_inspect` / `deepthonk.lock_reclaim` | Inspect lock ownership and explicitly reclaim only an exact fingerprint. |
|
|
193
|
+
| `deepthonk.repair_budget` | Replace legacy `[redacted]` numeric budget fields with explicit original values. |
|
|
191
194
|
| `deepthonk.run` | Blocking convenience: start + await completion in one call. Prefer `start` + polling for long-running jobs. |
|
|
192
195
|
| `deepthonk.rank` | Rank a user-supplied candidate set with pairwise judging + Bradley-Terry (skip generation). |
|
|
193
196
|
| `deepthonk.mutate` | Mutate one supplied candidate with critique (one-shot). |
|
|
@@ -209,8 +212,12 @@ All tools accept inline provider/model fields, or `config_path` pointing at a De
|
|
|
209
212
|
- `deepthonk://runs/{run_id}/population/{generation}` — population snapshot for a generation (JSON).
|
|
210
213
|
- `deepthonk://runs/{run_id}/{winner|final}` — text artifacts.
|
|
211
214
|
- `deepthonk://runs/{run_id}/status` — run state (JSON).
|
|
215
|
+
- `deepthonk://runs/{run_id}/prompts/{sha256}` — verified content-addressed rendered prompt (JSON).
|
|
216
|
+
- `deepthonk://runs/{run_id}/trace-v2` — bounded phase index; follow its manifest, commit, checkpoint-index, and checkpoint URIs to inspect every receipt.
|
|
212
217
|
- `deepthonk://jobs/{job_id}/{status|result|config|candidates|comparisons|scores|usage|trace|final|winner}?run_dir=...` — job-scoped lookup; the `run_dir` query param is required.
|
|
213
218
|
- `deepthonk://jobs/{job_id}/population/{generation}?run_dir=...` — job-scoped population snapshots before or after completion.
|
|
219
|
+
- Job results also include `prompt_template` and `trace_v2_template` for the job-scoped equivalents.
|
|
220
|
+
- `deepthonk://runs/{run_id}/{resource}/page/{cursor}` and job equivalents — opaque bounded pages. Whole reads are limited to 1 MiB; pages contain at most 1,000 records and 1 MiB.
|
|
214
221
|
|
|
215
222
|
### Prompts
|
|
216
223
|
|
|
@@ -218,7 +225,7 @@ Four templates mirror the core loop: `deepthonk/generate`, `deepthonk/compare`,
|
|
|
218
225
|
|
|
219
226
|
### Limits
|
|
220
227
|
|
|
221
|
-
`deepthonk.resume` reports trace state by default. With `continue: true`, it validates
|
|
228
|
+
`deepthonk.resume` reports trace state by default. With `continue: true`, it validates phase order, artifacts, run/provider/model identity, scores, usage, deterministic pair schedules, trace-v2 receipts, and phase commits. Valid incomplete-phase receipts are reused without another provider call. MCP Sampling model hints are preferences and token usage may be unavailable. Its shared adaptive limiter starts at 4; direct providers start at 8.
|
|
222
229
|
|
|
223
230
|
## Customization
|
|
224
231
|
|
|
@@ -229,6 +236,10 @@ Every algorithm dimension is reachable through MCP arguments and CLI flags:
|
|
|
229
236
|
- **Prompt style**: `general` or `paper-programming`.
|
|
230
237
|
- **Per-phase prompt templates**: override `generate`, `compare`, `mutate`, or `finalize` with custom system/user templates and variable substitution (`{task}`, `{rubric}`, `{candidate}`, `{candidateA}`, `{candidateB}`, `{critique}`). CLI supports `--prompts <yaml>` and `--prompts-json <json>`; MCP supports inline `prompts`. Unknown variables throw a fail-fast error at run-start.
|
|
231
238
|
- **Concurrency**: per-phase caps for `generate`, `judge`, `mutate`.
|
|
239
|
+
- **Provider backpressure**: `provider_max_concurrency` sets the recovery ceiling for the shared adaptive provider-route limiter.
|
|
240
|
+
- **Per-call output bounds**: independent generation, mutation, judge, and finalizer token caps.
|
|
241
|
+
- **Final ranking schedule**: seeded `all-pairs` or `k-regular`, with an explicit logical call cap.
|
|
242
|
+
- **Critique bound**: deterministic deduplication and a configurable aggregate-character cap before mutation.
|
|
232
243
|
|
|
233
244
|
Example agent call (MCP), no YAML file required:
|
|
234
245
|
|
|
@@ -266,18 +277,22 @@ runs/{run_id}/
|
|
|
266
277
|
usage.jsonl
|
|
267
278
|
population-0.json
|
|
268
279
|
population-{generation}.json
|
|
280
|
+
manifests/*.json
|
|
281
|
+
checkpoints/*/*.json
|
|
282
|
+
commits/*.json
|
|
269
283
|
summary.json
|
|
270
284
|
artifacts/winner.txt
|
|
271
285
|
artifacts/final.txt
|
|
286
|
+
artifacts/prompts/<sha256>.json # only when prompt capture is enabled
|
|
272
287
|
```
|
|
273
288
|
|
|
274
289
|
`status.json` is written when the run is launched through MCP `deepthonk.start` (so async clients can poll). `cancel.json` is written only when cancellation is requested. A `run.lock` file is held for the duration of the run.
|
|
275
290
|
|
|
276
|
-
|
|
291
|
+
Trace rows are batch-appended through a single writer and flushed before lifecycle boundaries. Per-item output and usage receipts are written atomically before a phase commit. If a run is killed mid-tournament, trace-v2 resume validates and reuses completed receipts, re-materializes their JSONL rows, and calls the provider only for missing or invalid work.
|
|
277
292
|
|
|
278
293
|
`usage.jsonl` carries one row per provider call (generator, judge, mutator, finalizer) with phase/role/provider/model/token/USD/latency/retry. It contains no prompt content. `jq` over `usage.jsonl` is the simplest way to break a run's cost down by role.
|
|
279
294
|
|
|
280
|
-
Prompts and raw model outputs are off by default. API keys are never written. Candidate answers, critiques, scores, and final artifacts are trace data and are stored/exported by design, so do not use a shared run directory for sensitive tasks unless that is acceptable.
|
|
295
|
+
Prompts and raw model outputs are off by default. When prompt capture is enabled, candidate/comparison rows carry a `promptRef` and identical rendered prompts share one content-addressed blob under `artifacts/prompts/`; the full prompt is not repeated in every JSONL row or receipt. API keys are never written. Candidate answers, critiques, scores, and final artifacts are trace data and are stored/exported by design, so do not use a shared run directory for sensitive tasks unless that is acceptable.
|
|
281
296
|
|
|
282
297
|
## Development
|
|
283
298
|
|
|
@@ -294,8 +309,8 @@ Release publishing is tag-triggered through npm Trusted Publishing. See [docs/re
|
|
|
294
309
|
|
|
295
310
|
Known limits for this release:
|
|
296
311
|
|
|
297
|
-
-
|
|
298
|
-
-
|
|
312
|
+
- Trace-v1 resume replays an interrupted phase wholesale; per-item reuse requires a trace-v2 run created by DeepThonk v0.2.
|
|
313
|
+
- HTTP Sampling is blocking-only. Use stdio or a direct provider for background `deepthonk.start` work.
|
|
299
314
|
- `maxUsd` requires known model pricing; add explicit prices in YAML for custom providers and model IDs. Optional fields `longContextThresholdTokens`, `inputUsdPerMillionLong`, and `outputUsdPerMillionLong` enable tiered pricing for models with a long-context surcharge (e.g., Gemini at 200K input tokens). Cache hit/miss rates remain flat.
|
|
300
315
|
|
|
301
316
|
## Acknowledgments
|
package/dist/defaults.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface ProviderModelOverrides {
|
|
|
9
9
|
}
|
|
10
10
|
export interface ProviderConfigOptions {
|
|
11
11
|
provider: string;
|
|
12
|
+
routeFingerprint?: string;
|
|
13
|
+
providerMaxConcurrency?: number;
|
|
12
14
|
baseUrl?: string;
|
|
13
15
|
apiKeyEnv?: string;
|
|
14
16
|
apiKey?: string;
|
|
@@ -16,6 +18,14 @@ export interface ProviderConfigOptions {
|
|
|
16
18
|
retry?: ProviderConfig["retry"];
|
|
17
19
|
supportsJsonMode?: boolean;
|
|
18
20
|
samplingTransport?: SamplingTransport;
|
|
21
|
+
modelHints?: string[];
|
|
22
|
+
costPriority?: number;
|
|
23
|
+
speedPriority?: number;
|
|
24
|
+
intelligencePriority?: number;
|
|
25
|
+
includeRawOutputs?: boolean;
|
|
26
|
+
requestTimeoutMs?: number;
|
|
27
|
+
/** Disable implicit endpoint and credential defaults for an explicitly replaced route. */
|
|
28
|
+
inheritProviderDefaults?: boolean;
|
|
19
29
|
roleProviders?: Partial<Record<ProviderRole, Partial<Omit<RoleProviderConfig, "model">> & {
|
|
20
30
|
model?: string;
|
|
21
31
|
}>>;
|
package/dist/defaults.js
CHANGED
|
@@ -8,16 +8,25 @@ export function resolveProviderModels(provider, overrides = {}) {
|
|
|
8
8
|
}
|
|
9
9
|
export function resolveProviderConfig(options) {
|
|
10
10
|
const models = resolveProviderModels(options.provider, options.models);
|
|
11
|
+
const inheritProviderDefaults = options.inheritProviderDefaults ?? true;
|
|
11
12
|
return {
|
|
12
13
|
provider: options.provider,
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
routeFingerprint: options.routeFingerprint,
|
|
15
|
+
providerMaxConcurrency: options.providerMaxConcurrency,
|
|
16
|
+
baseUrl: options.baseUrl ?? (inheritProviderDefaults ? defaultBaseUrl(options.provider) : undefined),
|
|
17
|
+
apiKeyEnv: options.apiKeyEnv ?? (inheritProviderDefaults ? defaultApiKeyEnv(options.provider) : undefined),
|
|
15
18
|
apiKey: options.apiKey,
|
|
16
19
|
models,
|
|
17
20
|
retry: options.retry,
|
|
18
21
|
supportsJsonMode: options.supportsJsonMode,
|
|
19
22
|
samplingTransport: options.samplingTransport,
|
|
20
|
-
|
|
23
|
+
modelHints: options.modelHints,
|
|
24
|
+
costPriority: options.costPriority,
|
|
25
|
+
speedPriority: options.speedPriority,
|
|
26
|
+
intelligencePriority: options.intelligencePriority,
|
|
27
|
+
includeRawOutputs: options.includeRawOutputs,
|
|
28
|
+
requestTimeoutMs: options.requestTimeoutMs,
|
|
29
|
+
roleProviders: resolveRoleProviders(options.provider, models, options.roleProviders, options.retry, options.supportsJsonMode, options.providerMaxConcurrency)
|
|
21
30
|
};
|
|
22
31
|
}
|
|
23
32
|
export function defaultModel(provider, role) {
|
|
@@ -49,7 +58,7 @@ export function defaultApiKeyEnv(provider) {
|
|
|
49
58
|
return "DEEPTHONK_API_KEY";
|
|
50
59
|
return undefined;
|
|
51
60
|
}
|
|
52
|
-
function resolveRoleProviders(baseProvider, models, roleProviders, baseRetry, baseSupportsJsonMode) {
|
|
61
|
+
function resolveRoleProviders(baseProvider, models, roleProviders, baseRetry, baseSupportsJsonMode, providerMaxConcurrency) {
|
|
53
62
|
if (!roleProviders)
|
|
54
63
|
return undefined;
|
|
55
64
|
const resolved = {};
|
|
@@ -58,15 +67,17 @@ function resolveRoleProviders(baseProvider, models, roleProviders, baseRetry, ba
|
|
|
58
67
|
if (!input)
|
|
59
68
|
continue;
|
|
60
69
|
const provider = input.provider ?? baseProvider;
|
|
61
|
-
const
|
|
70
|
+
const isolatedRoute = input.baseUrl !== undefined || (input.provider !== undefined && input.provider !== baseProvider);
|
|
71
|
+
const model = input.model ?? (isolatedRoute ? defaultModel(provider, role) : models[role]) ?? defaultModel(provider, role);
|
|
62
72
|
resolved[role] = {
|
|
63
73
|
provider,
|
|
64
74
|
baseUrl: input.baseUrl ?? defaultBaseUrl(provider),
|
|
65
|
-
apiKeyEnv: input.apiKeyEnv ?? defaultApiKeyEnv(provider),
|
|
75
|
+
apiKeyEnv: input.apiKeyEnv ?? (input.baseUrl === undefined ? defaultApiKeyEnv(provider) : undefined),
|
|
66
76
|
apiKey: input.apiKey,
|
|
67
77
|
model,
|
|
78
|
+
providerMaxConcurrency,
|
|
68
79
|
retry: input.retry ?? baseRetry,
|
|
69
|
-
supportsJsonMode: input.supportsJsonMode ?? baseSupportsJsonMode
|
|
80
|
+
supportsJsonMode: input.supportsJsonMode ?? (isolatedRoute ? undefined : baseSupportsJsonMode)
|
|
70
81
|
};
|
|
71
82
|
}
|
|
72
83
|
return Object.keys(resolved).length ? resolved : undefined;
|
package/dist/defaults.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type BuiltInProfileName, type RunConfig } from "@deepthonk/core";
|
|
2
|
+
import type { ProviderRole } from "./types.js";
|
|
3
|
+
export interface ExternalConfigFile {
|
|
4
|
+
runId?: string;
|
|
5
|
+
profile?: BuiltInProfileName;
|
|
6
|
+
provider?: string;
|
|
7
|
+
base_url?: string;
|
|
8
|
+
api_key_env?: string;
|
|
9
|
+
supports_json_mode?: boolean;
|
|
10
|
+
models?: {
|
|
11
|
+
generator?: string;
|
|
12
|
+
mutator?: string;
|
|
13
|
+
judge?: string;
|
|
14
|
+
finalizer?: string;
|
|
15
|
+
};
|
|
16
|
+
providers?: Partial<Record<ProviderRole, ExternalRoleProviderConfig>>;
|
|
17
|
+
concurrency?: Partial<RunConfig["concurrency"]>;
|
|
18
|
+
retry?: Partial<RunConfig["retry"]>;
|
|
19
|
+
budget?: RunConfig["budget"];
|
|
20
|
+
output?: Partial<RunConfig["output"]>;
|
|
21
|
+
prompt_style?: RunConfig["promptStyle"];
|
|
22
|
+
algorithm?: ExternalAlgorithmOverrides;
|
|
23
|
+
prompts?: ExternalPromptOverrides;
|
|
24
|
+
modelOutputTokens?: RunConfig["modelOutputTokens"];
|
|
25
|
+
critiqueLimits?: RunConfig["critiqueLimits"];
|
|
26
|
+
rank?: RunConfig["rank"];
|
|
27
|
+
providerMaxConcurrency?: number;
|
|
28
|
+
metadata?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
export interface ExternalAlgorithmOverrides {
|
|
31
|
+
n?: number;
|
|
32
|
+
k?: number;
|
|
33
|
+
t?: number;
|
|
34
|
+
m?: number;
|
|
35
|
+
lambda?: number;
|
|
36
|
+
sample_temperature?: number;
|
|
37
|
+
mutate_temperature?: number;
|
|
38
|
+
judge_temperature?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface ExternalPromptOverrides {
|
|
41
|
+
generate?: {
|
|
42
|
+
system?: string;
|
|
43
|
+
user?: string;
|
|
44
|
+
};
|
|
45
|
+
compare?: {
|
|
46
|
+
system?: string;
|
|
47
|
+
user?: string;
|
|
48
|
+
};
|
|
49
|
+
mutate?: {
|
|
50
|
+
system?: string;
|
|
51
|
+
user?: string;
|
|
52
|
+
};
|
|
53
|
+
finalize?: {
|
|
54
|
+
system?: string;
|
|
55
|
+
user?: string;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
interface ExternalRoleProviderConfig {
|
|
59
|
+
provider?: string;
|
|
60
|
+
base_url?: string;
|
|
61
|
+
api_key_env?: string;
|
|
62
|
+
model?: string;
|
|
63
|
+
supports_json_mode?: boolean;
|
|
64
|
+
}
|
|
65
|
+
/** Normalize the public YAML contract to the camelCase core/provider shape. */
|
|
66
|
+
export declare function normalizeExternalConfig(value: unknown, source?: string): ExternalConfigFile;
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { ConfigError, maxPhaseConcurrency } from "@deepthonk/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
const nonEmptyString = z.string().trim().min(1);
|
|
4
|
+
const promptBlockSchema = z.object({ system: z.string().optional(), user: z.string().optional() }).strict();
|
|
5
|
+
const priceSchema = z
|
|
6
|
+
.object({
|
|
7
|
+
provider: nonEmptyString,
|
|
8
|
+
model: nonEmptyString,
|
|
9
|
+
inputUsdPerMillion: z.number().min(0).optional(),
|
|
10
|
+
inputCacheHitUsdPerMillion: z.number().min(0).optional(),
|
|
11
|
+
inputCacheMissUsdPerMillion: z.number().min(0).optional(),
|
|
12
|
+
outputUsdPerMillion: z.number().min(0).optional(),
|
|
13
|
+
longContextThresholdTokens: z.number().int().min(1).optional(),
|
|
14
|
+
inputUsdPerMillionLong: z.number().min(0).optional(),
|
|
15
|
+
outputUsdPerMillionLong: z.number().min(0).optional()
|
|
16
|
+
})
|
|
17
|
+
.strict()
|
|
18
|
+
.superRefine((price, context) => {
|
|
19
|
+
const longFields = [price.longContextThresholdTokens, price.inputUsdPerMillionLong, price.outputUsdPerMillionLong];
|
|
20
|
+
if (longFields.every((entry) => entry === undefined))
|
|
21
|
+
return;
|
|
22
|
+
if (price.longContextThresholdTokens === undefined ||
|
|
23
|
+
price.inputUsdPerMillion === undefined ||
|
|
24
|
+
price.outputUsdPerMillion === undefined ||
|
|
25
|
+
price.inputUsdPerMillionLong === undefined ||
|
|
26
|
+
price.outputUsdPerMillionLong === undefined) {
|
|
27
|
+
context.addIssue({
|
|
28
|
+
code: z.ZodIssueCode.custom,
|
|
29
|
+
message: "Long-context pricing requires threshold and complete flat/long input/output rates."
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
const externalConfigSchema = z
|
|
34
|
+
.object({
|
|
35
|
+
runId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/).optional(),
|
|
36
|
+
profile: z.enum(["quick", "balanced", "paper"]).optional(),
|
|
37
|
+
provider: nonEmptyString.optional(),
|
|
38
|
+
base_url: z.string().url().optional(),
|
|
39
|
+
api_key_env: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).optional(),
|
|
40
|
+
supports_json_mode: z.boolean().optional(),
|
|
41
|
+
models: z.object({ generator: nonEmptyString.optional(), mutator: nonEmptyString.optional(), judge: nonEmptyString.optional(), finalizer: nonEmptyString.optional() }).strict().optional(),
|
|
42
|
+
providers: z.record(z.enum(["generator", "mutator", "judge", "finalizer"]), z.object({
|
|
43
|
+
provider: nonEmptyString.optional(),
|
|
44
|
+
base_url: z.string().url().optional(),
|
|
45
|
+
api_key_env: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).optional(),
|
|
46
|
+
model: nonEmptyString.optional(),
|
|
47
|
+
supports_json_mode: z.boolean().optional()
|
|
48
|
+
}).strict()).optional(),
|
|
49
|
+
concurrency: z.object({ generate: z.number().int().min(1).optional(), judge: z.number().int().min(1).optional(), mutate: z.number().int().min(1).optional() }).strict().optional(),
|
|
50
|
+
retry: z.object({ httpRetries: z.number().int().min(0).optional(), invalidJsonRetries: z.number().int().min(0).optional(), requestTimeoutMs: z.number().int().min(1).optional() }).strict().optional(),
|
|
51
|
+
budget: z.object({
|
|
52
|
+
maxCalls: z.number().int().min(1).optional(),
|
|
53
|
+
maxInputTokens: z.number().int().min(1).optional(),
|
|
54
|
+
maxOutputTokens: z.number().int().min(1).optional(),
|
|
55
|
+
maxUsd: z.number().min(0).optional(),
|
|
56
|
+
prices: z.array(priceSchema).optional()
|
|
57
|
+
}).strict().optional(),
|
|
58
|
+
output: z.object({ includeRawModelOutputs: z.boolean().optional(), includePrompts: z.boolean().optional() }).strict().optional(),
|
|
59
|
+
modelOutputTokens: z.object({
|
|
60
|
+
generation: z.number().int().min(1).optional(),
|
|
61
|
+
mutation: z.number().int().min(1).optional(),
|
|
62
|
+
judge: z.number().int().min(1).optional(),
|
|
63
|
+
finalizer: z.number().int().min(1).optional()
|
|
64
|
+
}).strict().optional(),
|
|
65
|
+
critiqueLimits: z.object({ aggregateChars: z.number().int().min(256).optional() }).strict().optional(),
|
|
66
|
+
rank: z.object({
|
|
67
|
+
mode: z.enum(["all-pairs", "k-regular"]),
|
|
68
|
+
k: z.number().int().min(1).optional(),
|
|
69
|
+
seed: z.number().int().optional(),
|
|
70
|
+
maxCalls: z.number().int().min(1).optional()
|
|
71
|
+
}).strict().optional(),
|
|
72
|
+
providerMaxConcurrency: z.number().int().min(1).max(maxPhaseConcurrency).optional(),
|
|
73
|
+
prompt_style: z.enum(["general", "paper-programming"]).optional(),
|
|
74
|
+
algorithm: z.object({
|
|
75
|
+
n: z.number().int().min(2).optional(),
|
|
76
|
+
k: z.number().int().min(1).optional(),
|
|
77
|
+
t: z.number().int().min(0).optional(),
|
|
78
|
+
m: z.number().int().min(1).optional(),
|
|
79
|
+
lambda: z.number().min(0).optional(),
|
|
80
|
+
sample_temperature: z.number().min(0).optional(),
|
|
81
|
+
mutate_temperature: z.number().min(0).optional(),
|
|
82
|
+
judge_temperature: z.number().min(0).optional()
|
|
83
|
+
}).strict().optional(),
|
|
84
|
+
prompts: z.object({ generate: promptBlockSchema.optional(), compare: promptBlockSchema.optional(), mutate: promptBlockSchema.optional(), finalize: promptBlockSchema.optional() }).strict().optional(),
|
|
85
|
+
metadata: z.record(z.unknown()).optional()
|
|
86
|
+
})
|
|
87
|
+
.strict();
|
|
88
|
+
/** Normalize the public YAML contract to the camelCase core/provider shape. */
|
|
89
|
+
export function normalizeExternalConfig(value, source = "config") {
|
|
90
|
+
const root = configRecord(value, source);
|
|
91
|
+
assertKnownKeys(root, [
|
|
92
|
+
"profile", "run_id", "runId", "provider", "base_url", "baseUrl", "api_key_env", "apiKeyEnv",
|
|
93
|
+
"supports_json_mode", "supportsJsonMode", "models", "providers", "concurrency", "retry", "budget",
|
|
94
|
+
"output", "prompt_style", "promptStyle", "algorithm", "prompts", "model_output_tokens", "modelOutputTokens",
|
|
95
|
+
"critique_limits", "critiqueLimits", "rank", "provider_max_concurrency", "providerMaxConcurrency", "metadata"
|
|
96
|
+
], source);
|
|
97
|
+
const normalized = {
|
|
98
|
+
runId: aliased(root, "run_id", "runId", source),
|
|
99
|
+
profile: root.profile,
|
|
100
|
+
provider: root.provider,
|
|
101
|
+
base_url: aliased(root, "base_url", "baseUrl", source),
|
|
102
|
+
api_key_env: aliased(root, "api_key_env", "apiKeyEnv", source),
|
|
103
|
+
supports_json_mode: aliased(root, "supports_json_mode", "supportsJsonMode", source),
|
|
104
|
+
models: normalizeModels(root.models, `${source}.models`),
|
|
105
|
+
providers: normalizeProviders(root.providers, `${source}.providers`),
|
|
106
|
+
concurrency: normalizeConcurrency(root.concurrency, `${source}.concurrency`),
|
|
107
|
+
retry: normalizeRetry(root.retry, `${source}.retry`),
|
|
108
|
+
budget: normalizeBudget(root.budget, `${source}.budget`),
|
|
109
|
+
output: normalizeOutput(root.output, `${source}.output`),
|
|
110
|
+
prompt_style: aliased(root, "prompt_style", "promptStyle", source),
|
|
111
|
+
algorithm: normalizeAlgorithm(root.algorithm, `${source}.algorithm`),
|
|
112
|
+
prompts: normalizePrompts(root.prompts, `${source}.prompts`),
|
|
113
|
+
modelOutputTokens: normalizeModelOutputTokens(aliased(root, "model_output_tokens", "modelOutputTokens", source), `${source}.model_output_tokens`),
|
|
114
|
+
critiqueLimits: normalizeCritiqueLimits(aliased(root, "critique_limits", "critiqueLimits", source), `${source}.critique_limits`),
|
|
115
|
+
rank: normalizeRank(root.rank, `${source}.rank`),
|
|
116
|
+
providerMaxConcurrency: aliased(root, "provider_max_concurrency", "providerMaxConcurrency", source),
|
|
117
|
+
metadata: root.metadata === undefined ? undefined : configRecord(root.metadata, `${source}.metadata`)
|
|
118
|
+
};
|
|
119
|
+
const compact = compactConfig(normalized);
|
|
120
|
+
const parsed = externalConfigSchema.safeParse(compact);
|
|
121
|
+
if (!parsed.success) {
|
|
122
|
+
throw new ConfigError(`Invalid config value at ${source}: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`).join("; ")}.`, {
|
|
123
|
+
code: "config.invalid_value",
|
|
124
|
+
retryable: false,
|
|
125
|
+
fix: "Use the documented strings, booleans, enums, and numeric ranges. YAML numeric values must not be quoted."
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return parsed.data;
|
|
129
|
+
}
|
|
130
|
+
function normalizeModels(value, path) {
|
|
131
|
+
if (value === undefined)
|
|
132
|
+
return undefined;
|
|
133
|
+
const record = configRecord(value, path);
|
|
134
|
+
assertKnownKeys(record, ["generator", "mutator", "judge", "finalizer"], path);
|
|
135
|
+
return compactConfig({ generator: record.generator, mutator: record.mutator, judge: record.judge, finalizer: record.finalizer });
|
|
136
|
+
}
|
|
137
|
+
function normalizeProviders(value, path) {
|
|
138
|
+
if (value === undefined)
|
|
139
|
+
return undefined;
|
|
140
|
+
const record = configRecord(value, path);
|
|
141
|
+
assertKnownKeys(record, ["generator", "mutator", "judge", "finalizer"], path);
|
|
142
|
+
const providers = {};
|
|
143
|
+
for (const role of ["generator", "mutator", "judge", "finalizer"]) {
|
|
144
|
+
if (record[role] === undefined)
|
|
145
|
+
continue;
|
|
146
|
+
const rolePath = `${path}.${role}`;
|
|
147
|
+
const input = configRecord(record[role], rolePath);
|
|
148
|
+
assertKnownKeys(input, ["provider", "base_url", "baseUrl", "api_key_env", "apiKeyEnv", "model", "supports_json_mode", "supportsJsonMode"], rolePath);
|
|
149
|
+
providers[role] = compactConfig({
|
|
150
|
+
provider: input.provider,
|
|
151
|
+
base_url: aliased(input, "base_url", "baseUrl", rolePath),
|
|
152
|
+
api_key_env: aliased(input, "api_key_env", "apiKeyEnv", rolePath),
|
|
153
|
+
model: input.model,
|
|
154
|
+
supports_json_mode: aliased(input, "supports_json_mode", "supportsJsonMode", rolePath)
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return providers;
|
|
158
|
+
}
|
|
159
|
+
function normalizeConcurrency(value, path) {
|
|
160
|
+
if (value === undefined)
|
|
161
|
+
return undefined;
|
|
162
|
+
const record = configRecord(value, path);
|
|
163
|
+
assertKnownKeys(record, ["generate", "judge", "mutate"], path);
|
|
164
|
+
return compactConfig({ generate: record.generate, judge: record.judge, mutate: record.mutate });
|
|
165
|
+
}
|
|
166
|
+
function normalizeRetry(value, path) {
|
|
167
|
+
if (value === undefined)
|
|
168
|
+
return undefined;
|
|
169
|
+
const record = configRecord(value, path);
|
|
170
|
+
assertKnownKeys(record, ["http_retries", "httpRetries", "invalid_json_retries", "invalidJsonRetries", "request_timeout_ms", "requestTimeoutMs"], path);
|
|
171
|
+
return compactConfig({
|
|
172
|
+
httpRetries: aliased(record, "http_retries", "httpRetries", path),
|
|
173
|
+
invalidJsonRetries: aliased(record, "invalid_json_retries", "invalidJsonRetries", path),
|
|
174
|
+
requestTimeoutMs: aliased(record, "request_timeout_ms", "requestTimeoutMs", path)
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function normalizeBudget(value, path) {
|
|
178
|
+
if (value === undefined)
|
|
179
|
+
return undefined;
|
|
180
|
+
const record = configRecord(value, path);
|
|
181
|
+
assertKnownKeys(record, ["max_calls", "maxCalls", "max_input_tokens", "maxInputTokens", "max_output_tokens", "maxOutputTokens", "max_usd", "maxUsd", "prices"], path);
|
|
182
|
+
return compactConfig({
|
|
183
|
+
maxCalls: aliased(record, "max_calls", "maxCalls", path),
|
|
184
|
+
maxInputTokens: aliased(record, "max_input_tokens", "maxInputTokens", path),
|
|
185
|
+
maxOutputTokens: aliased(record, "max_output_tokens", "maxOutputTokens", path),
|
|
186
|
+
maxUsd: aliased(record, "max_usd", "maxUsd", path),
|
|
187
|
+
prices: record.prices === undefined ? undefined : normalizePrices(record.prices, `${path}.prices`)
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
function normalizePrices(value, path) {
|
|
191
|
+
if (!Array.isArray(value))
|
|
192
|
+
throw invalidConfigShape(path, "an array");
|
|
193
|
+
return value.map((entry, index) => {
|
|
194
|
+
const entryPath = `${path}[${index}]`;
|
|
195
|
+
const record = configRecord(entry, entryPath);
|
|
196
|
+
const aliases = [
|
|
197
|
+
["input_usd_per_million", "inputUsdPerMillion"], ["input_cache_hit_usd_per_million", "inputCacheHitUsdPerMillion"],
|
|
198
|
+
["input_cache_miss_usd_per_million", "inputCacheMissUsdPerMillion"], ["output_usd_per_million", "outputUsdPerMillion"],
|
|
199
|
+
["long_context_threshold_tokens", "longContextThresholdTokens"], ["input_usd_per_million_long", "inputUsdPerMillionLong"],
|
|
200
|
+
["output_usd_per_million_long", "outputUsdPerMillionLong"]
|
|
201
|
+
];
|
|
202
|
+
assertKnownKeys(record, ["provider", "model", ...aliases.flat()], entryPath);
|
|
203
|
+
return compactConfig({
|
|
204
|
+
provider: record.provider, model: record.model,
|
|
205
|
+
inputUsdPerMillion: aliased(record, ...aliases[0], entryPath),
|
|
206
|
+
inputCacheHitUsdPerMillion: aliased(record, ...aliases[1], entryPath),
|
|
207
|
+
inputCacheMissUsdPerMillion: aliased(record, ...aliases[2], entryPath),
|
|
208
|
+
outputUsdPerMillion: aliased(record, ...aliases[3], entryPath),
|
|
209
|
+
longContextThresholdTokens: aliased(record, ...aliases[4], entryPath),
|
|
210
|
+
inputUsdPerMillionLong: aliased(record, ...aliases[5], entryPath),
|
|
211
|
+
outputUsdPerMillionLong: aliased(record, ...aliases[6], entryPath)
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function normalizeOutput(value, path) {
|
|
216
|
+
if (value === undefined)
|
|
217
|
+
return undefined;
|
|
218
|
+
const record = configRecord(value, path);
|
|
219
|
+
assertKnownKeys(record, ["include_raw_model_outputs", "includeRawModelOutputs", "include_prompts", "includePrompts"], path);
|
|
220
|
+
return compactConfig({
|
|
221
|
+
includeRawModelOutputs: aliased(record, "include_raw_model_outputs", "includeRawModelOutputs", path),
|
|
222
|
+
includePrompts: aliased(record, "include_prompts", "includePrompts", path)
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
function normalizeAlgorithm(value, path) {
|
|
226
|
+
if (value === undefined)
|
|
227
|
+
return undefined;
|
|
228
|
+
const record = configRecord(value, path);
|
|
229
|
+
assertKnownKeys(record, ["n", "k", "t", "m", "lambda", "sample_temperature", "sampleTemperature", "mutate_temperature", "mutateTemperature", "judge_temperature", "judgeTemperature"], path);
|
|
230
|
+
return compactConfig({
|
|
231
|
+
n: record.n, k: record.k, t: record.t, m: record.m, lambda: record.lambda,
|
|
232
|
+
sample_temperature: aliased(record, "sample_temperature", "sampleTemperature", path),
|
|
233
|
+
mutate_temperature: aliased(record, "mutate_temperature", "mutateTemperature", path),
|
|
234
|
+
judge_temperature: aliased(record, "judge_temperature", "judgeTemperature", path)
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function normalizePrompts(value, path) {
|
|
238
|
+
if (value === undefined)
|
|
239
|
+
return undefined;
|
|
240
|
+
const record = configRecord(value, path);
|
|
241
|
+
assertKnownKeys(record, ["generate", "compare", "mutate", "finalize"], path);
|
|
242
|
+
const prompts = {};
|
|
243
|
+
for (const phase of ["generate", "compare", "mutate", "finalize"]) {
|
|
244
|
+
if (record[phase] === undefined)
|
|
245
|
+
continue;
|
|
246
|
+
const phasePath = `${path}.${phase}`;
|
|
247
|
+
const prompt = configRecord(record[phase], phasePath);
|
|
248
|
+
assertKnownKeys(prompt, ["system", "user"], phasePath);
|
|
249
|
+
prompts[phase] = compactConfig({ system: prompt.system, user: prompt.user });
|
|
250
|
+
}
|
|
251
|
+
return prompts;
|
|
252
|
+
}
|
|
253
|
+
function normalizeModelOutputTokens(value, path) {
|
|
254
|
+
if (value === undefined)
|
|
255
|
+
return undefined;
|
|
256
|
+
const record = configRecord(value, path);
|
|
257
|
+
assertKnownKeys(record, ["generation", "mutation", "judge", "finalizer"], path);
|
|
258
|
+
return compactConfig({
|
|
259
|
+
generation: record.generation,
|
|
260
|
+
mutation: record.mutation,
|
|
261
|
+
judge: record.judge,
|
|
262
|
+
finalizer: record.finalizer
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function normalizeCritiqueLimits(value, path) {
|
|
266
|
+
if (value === undefined)
|
|
267
|
+
return undefined;
|
|
268
|
+
const record = configRecord(value, path);
|
|
269
|
+
assertKnownKeys(record, ["aggregate_chars", "aggregateChars"], path);
|
|
270
|
+
return compactConfig({
|
|
271
|
+
aggregateChars: aliased(record, "aggregate_chars", "aggregateChars", path)
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function normalizeRank(value, path) {
|
|
275
|
+
if (value === undefined)
|
|
276
|
+
return undefined;
|
|
277
|
+
const record = configRecord(value, path);
|
|
278
|
+
assertKnownKeys(record, ["mode", "k", "seed", "max_calls", "maxCalls"], path);
|
|
279
|
+
return compactConfig({
|
|
280
|
+
mode: record.mode,
|
|
281
|
+
k: record.k,
|
|
282
|
+
seed: record.seed,
|
|
283
|
+
maxCalls: aliased(record, "max_calls", "maxCalls", path)
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
function configRecord(value, path) {
|
|
287
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
288
|
+
throw invalidConfigShape(path, "a mapping");
|
|
289
|
+
return value;
|
|
290
|
+
}
|
|
291
|
+
function assertKnownKeys(record, allowed, path) {
|
|
292
|
+
const allowedSet = new Set(allowed);
|
|
293
|
+
const unknown = Object.keys(record).filter((key) => !allowedSet.has(key));
|
|
294
|
+
if (!unknown.length)
|
|
295
|
+
return;
|
|
296
|
+
throw new ConfigError(`Unknown config key${unknown.length === 1 ? "" : "s"} at ${path}: ${unknown.join(", ")}.`, {
|
|
297
|
+
code: "config.unknown_key", retryable: false,
|
|
298
|
+
fix: "Remove misspelled or unsupported operational keys. Arbitrary application data is allowed only below metadata."
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
function aliased(record, canonical, camel, path) {
|
|
302
|
+
const hasCanonical = Object.prototype.hasOwnProperty.call(record, canonical);
|
|
303
|
+
const hasCamel = Object.prototype.hasOwnProperty.call(record, camel);
|
|
304
|
+
if (hasCanonical && hasCamel && JSON.stringify(record[canonical]) !== JSON.stringify(record[camel])) {
|
|
305
|
+
throw new ConfigError(`Conflicting config aliases at ${path}: '${canonical}' and '${camel}' have different values.`, {
|
|
306
|
+
code: "config.alias_conflict", retryable: false,
|
|
307
|
+
fix: `Keep the canonical snake_case '${canonical}' key, or make both aliases identical during migration.`
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
return hasCanonical ? record[canonical] : record[camel];
|
|
311
|
+
}
|
|
312
|
+
function compactConfig(value) {
|
|
313
|
+
return Object.fromEntries(Object.entries(value).filter(([, entryValue]) => entryValue !== undefined));
|
|
314
|
+
}
|
|
315
|
+
function invalidConfigShape(path, expected) {
|
|
316
|
+
return new ConfigError(`${path} must be ${expected}.`, { code: "config.invalid_shape", retryable: false, fix: "Use the documented YAML mapping and array structure." });
|
|
317
|
+
}
|
|
318
|
+
//# sourceMappingURL=externalConfig.js.map
|