mercury-agent 0.5.0 → 0.5.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 +452 -451
- package/container/Dockerfile +127 -127
- package/container/Dockerfile.base +109 -109
- package/container/Dockerfile.power +17 -17
- package/container/agent-package.json +8 -8
- package/container/build.sh +54 -54
- package/docs/ROADMAP.md +3 -4
- package/docs/archive/summarization/2026-07-02-recent-dev-summary.md +71 -0
- package/docs/auth/dashboard.md +28 -28
- package/docs/auth/overview.md +109 -109
- package/docs/auth/whatsapp.md +173 -173
- package/docs/authoring-profiles.md +174 -174
- package/docs/configuration.md +54 -54
- package/docs/container-lifecycle.md +349 -349
- package/docs/{bugs/bwrap-privileged-linux-docker.md → debug/major/2026-07-02-bwrap-privileged-linux-docker.md} +21 -1
- package/docs/{bugs/e2big-prompt-delivery-fix.md → debug/major/2026-07-02-e2big-prompt-delivery-fix.md} +27 -1
- package/docs/{bugs/registry-pull-no-local-fallback.md → debug/major/2026-07-02-registry-pull-no-local-fallback.md} +27 -1
- package/docs/debug/summarization/2026-07-02-common-bug-patterns.md +72 -0
- package/docs/deployment.md +199 -199
- package/docs/extensions.md +375 -375
- package/docs/graceful-shutdown.md +62 -62
- package/docs/kb-distillation.md +77 -77
- package/docs/media/overview.md +140 -140
- package/docs/media/whatsapp.md +171 -171
- package/docs/memory.md +137 -137
- package/docs/permissions.md +217 -217
- package/docs/pipeline.md +228 -228
- package/docs/prd-chat-memory.md +76 -76
- package/docs/prd-config-load.md +82 -82
- package/docs/rate-limiting.md +229 -229
- package/docs/runbooks/publish-checklist.md +9 -1
- package/docs/scheduler.md +288 -288
- package/docs/setup-discord.md +100 -100
- package/docs/setup-slack.md +119 -119
- package/docs/setup-whatsapp.md +94 -94
- package/docs/subagents.md +166 -166
- package/docs/web-search.md +62 -62
- package/examples/extensions/README.md +12 -12
- package/examples/extensions/charts/index.ts +13 -13
- package/examples/extensions/charts/skill/SKILL.md +98 -98
- package/examples/extensions/gws/README.md +52 -52
- package/examples/extensions/gws/skill/SKILL.md +57 -57
- package/examples/extensions/gws/skill/references/calendar.md +101 -101
- package/examples/extensions/gws/skill/references/docs.md +65 -65
- package/examples/extensions/gws/skill/references/drive.md +79 -79
- package/examples/extensions/gws/skill/references/gmail.md +85 -85
- package/examples/extensions/gws/skill/references/sheets.md +60 -60
- package/examples/extensions/napkin/skill/SKILL.md +728 -728
- package/examples/extensions/pdf/skill/LICENSE.txt +30 -30
- package/examples/extensions/pdf/skill/SKILL.md +314 -314
- package/examples/extensions/pdf/skill/forms.md +294 -294
- package/examples/extensions/pdf/skill/reference.md +611 -611
- package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +65 -65
- package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +11 -11
- package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +33 -33
- package/examples/extensions/pdf/skill/scripts/create_validation_image.py +37 -37
- package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +122 -122
- package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +115 -115
- package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +98 -98
- package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +107 -107
- package/examples/extensions/permission-guard/index.ts +65 -65
- package/examples/extensions/pinchtab/skill/SKILL.md +224 -224
- package/examples/extensions/pinchtab/skill/TRUST.md +69 -69
- package/examples/extensions/pinchtab/skill/references/api.md +297 -297
- package/examples/extensions/pinchtab/skill/references/env.md +45 -45
- package/examples/extensions/pinchtab/skill/references/profiles.md +107 -107
- package/examples/extensions/tradestation/host/refresh.ts +102 -102
- package/examples/extensions/tradestation/index.ts +153 -153
- package/examples/extensions/tradestation/skill/SKILL.md +67 -67
- package/examples/extensions/voice-synth/index.ts +94 -94
- package/examples/extensions/voice-synth/skill/SKILL.md +38 -38
- package/examples/extensions/voice-transcribe/requirements.txt +8 -8
- package/examples/extensions/voice-transcribe/scripts/transcribe.py +179 -179
- package/examples/extensions/voice-transcribe/skill/SKILL.md +53 -53
- package/examples/extensions/yahoo-mail/cli/package.json +13 -13
- package/examples/extensions/yahoo-mail/skill/SKILL.md +78 -78
- package/package.json +106 -106
- package/resources/agents/explore.md +50 -50
- package/resources/agents/worker.md +24 -24
- package/resources/connection-env-vars.json +25 -25
- package/resources/pi-extensions/subagent/agents.ts +126 -126
- package/resources/pi-extensions/subagent/index.ts +964 -964
- package/resources/profiles/coding/AGENTS.md +43 -43
- package/resources/profiles/coding/mercury-profile.yaml +15 -15
- package/resources/profiles/general/AGENTS.md +31 -31
- package/resources/profiles/general/mercury-profile.yaml +15 -15
- package/resources/profiles/research/AGENTS.md +40 -40
- package/resources/profiles/research/mercury-profile.yaml +15 -15
- package/resources/skills/config/SKILL.md +25 -25
- package/resources/skills/context/SKILL.md +33 -33
- package/resources/skills/conversation-recap/SKILL.md +19 -19
- package/resources/skills/mutes/SKILL.md +31 -31
- package/resources/skills/permissions/SKILL.md +19 -19
- package/resources/skills/preferences/SKILL.md +31 -31
- package/resources/skills/recall/SKILL.md +24 -24
- package/resources/skills/roles/SKILL.md +18 -18
- package/resources/skills/spaces/SKILL.md +18 -18
- package/resources/skills/tasks/SKILL.md +45 -45
- package/resources/templates/AGENTS.md +157 -157
- package/resources/templates/env.template +38 -38
- package/resources/templates/mercury.example.yaml +99 -99
- package/src/agent/container-entry.ts +1 -1
- package/src/agent/container-runner.ts +1345 -1346
- package/src/cli/mercury.ts +39 -7
- package/src/cli/mrctl.ts +636 -636
- package/src/config-file.ts +540 -540
- package/src/config.ts +339 -339
- package/src/core/api.ts +125 -125
- package/src/core/caller-token.ts +101 -101
- package/src/core/permissions.ts +228 -228
- package/src/core/profiles.ts +271 -271
- package/src/core/routes/capability.ts +70 -70
- package/src/core/routes/index.ts +15 -15
- package/src/core/runtime.ts +1530 -1530
- package/src/dashboard/index.html +729 -729
- package/src/extensions/api.ts +273 -273
- package/src/extensions/loader.ts +286 -286
- package/src/extensions/types.ts +517 -517
- package/src/main.ts +605 -605
- package/docs/pending-updates/applicative-profiles.md +0 -15
- package/docs/pending-updates/caller-bound-capability-token.md +0 -15
- package/docs/pending-updates/dm-auto-space.md +0 -15
- /package/docs/archive/{2026-07-01-applicative-profiles.md → applicative-profiles/2026-07-01-applicative-profiles.md} +0 -0
- /package/docs/archive/{2026-07-01-caller-bound-capability-token.md → applicative-profiles/2026-07-01-caller-bound-capability-token.md} +0 -0
- /package/docs/archive/{2026-07-01-dm-auto-space.md → applicative-profiles/2026-07-01-dm-auto-space.md} +0 -0
package/src/config.ts
CHANGED
|
@@ -1,339 +1,339 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import {
|
|
4
|
-
type ModelCapabilities,
|
|
5
|
-
parseModelCapabilitiesEnv,
|
|
6
|
-
resolveModelChainCapabilities,
|
|
7
|
-
} from "./agent/model-capabilities.js";
|
|
8
|
-
import { mergeRawMercuryConfig } from "./config-file.js";
|
|
9
|
-
import { parseModelLegsArray } from "./config-model-chain.js";
|
|
10
|
-
|
|
11
|
-
/** One model leg in the ordered fallback chain (primary first). */
|
|
12
|
-
export type ModelLeg = { provider: string; model: string };
|
|
13
|
-
|
|
14
|
-
function parseModelChainJson(raw: string): ModelLeg[] {
|
|
15
|
-
let parsed: unknown;
|
|
16
|
-
try {
|
|
17
|
-
parsed = JSON.parse(raw);
|
|
18
|
-
} catch {
|
|
19
|
-
throw new Error("MERCURY_MODEL_CHAIN must be valid JSON array");
|
|
20
|
-
}
|
|
21
|
-
return parseModelLegsArray(parsed, "MERCURY_MODEL_CHAIN");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function resolveModelChain(base: {
|
|
25
|
-
modelChain: string | undefined;
|
|
26
|
-
modelProvider: string;
|
|
27
|
-
model: string;
|
|
28
|
-
modelFallbackProvider: string | undefined;
|
|
29
|
-
modelFallback: string | undefined;
|
|
30
|
-
}): ModelLeg[] {
|
|
31
|
-
const trimmed = base.modelChain?.trim();
|
|
32
|
-
if (trimmed) {
|
|
33
|
-
return parseModelChainJson(trimmed);
|
|
34
|
-
}
|
|
35
|
-
const legs: ModelLeg[] = [
|
|
36
|
-
{ provider: base.modelProvider, model: base.model },
|
|
37
|
-
];
|
|
38
|
-
const fp = base.modelFallbackProvider?.trim();
|
|
39
|
-
const fm = base.modelFallback?.trim();
|
|
40
|
-
if (fp && fm) {
|
|
41
|
-
legs.push({ provider: fp, model: fm });
|
|
42
|
-
}
|
|
43
|
-
return legs;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Parse boolean from env var strings — case-insensitive "true"/"1" → true, everything else → false */
|
|
47
|
-
const booleanFromEnv = z.union([z.boolean(), z.string()]).transform((val) => {
|
|
48
|
-
if (typeof val === "boolean") return val;
|
|
49
|
-
const lower = val.toLowerCase();
|
|
50
|
-
return lower === "true" || lower === "1";
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const schema = z.object({
|
|
54
|
-
// ─── API Key Mode ───────────────────────────────────────────────────
|
|
55
|
-
apiKeyMode: z.enum(["platform", "byok"]).default("platform"),
|
|
56
|
-
|
|
57
|
-
// ─── Logging ────────────────────────────────────────────────────────
|
|
58
|
-
logLevel: z
|
|
59
|
-
.enum(["debug", "info", "warn", "error", "silent"])
|
|
60
|
-
.default("info"),
|
|
61
|
-
logFormat: z.enum(["text", "json"]).default("text"),
|
|
62
|
-
|
|
63
|
-
// ─── AI Model ───────────────────────────────────────────────────────
|
|
64
|
-
modelProvider: z.string().default("anthropic"),
|
|
65
|
-
model: z.string().default("claude-opus-4-
|
|
66
|
-
modelFallbackProvider: z.string().optional(),
|
|
67
|
-
modelFallback: z.string().optional(),
|
|
68
|
-
/** JSON array of `{ provider, model }`. When set, overrides legacy primary+fallback pair. */
|
|
69
|
-
modelChain: z.string().optional(),
|
|
70
|
-
/** Extra attempts after the first failure on the same leg (retryable errors only). Default 2 => 3 tries max per leg. */
|
|
71
|
-
modelMaxRetriesPerLeg: z.coerce.number().int().min(0).max(5).default(2),
|
|
72
|
-
/** Wall-clock budget for the whole chain (ms). Clamped below container timeout. Default 120s. */
|
|
73
|
-
modelChainBudgetMs: z.coerce
|
|
74
|
-
.number()
|
|
75
|
-
.int()
|
|
76
|
-
.min(5000)
|
|
77
|
-
.max(55 * 60 * 1000)
|
|
78
|
-
.default(120_000),
|
|
79
|
-
/**
|
|
80
|
-
* Optional JSON object overriding model capabilities for all chain legs, e.g.
|
|
81
|
-
* `{"tools":false,"vision":true}`. Highest priority over YAML and built-in map.
|
|
82
|
-
*/
|
|
83
|
-
modelCapabilitiesEnv: z.string().optional(),
|
|
84
|
-
|
|
85
|
-
// ─── Trigger Behavior ───────────────────────────────────────────────
|
|
86
|
-
triggerPatterns: z.string().default("@Mercury,Mercury"),
|
|
87
|
-
triggerMatch: z.string().default("mention"),
|
|
88
|
-
|
|
89
|
-
// ─── Context Behavior ───────────────────────────────────────────────
|
|
90
|
-
/** Default context mode seeded into the `main` space on first boot. */
|
|
91
|
-
contextMode: z.enum(["clear", "context"]).default("context"),
|
|
92
|
-
/** Default sliding-window turn count for `context` mode (1–50). Seeded into `main` on first boot. */
|
|
93
|
-
contextWindowSize: z.coerce.number().int().min(1).max(50).default(10),
|
|
94
|
-
/** Default reply-chain depth for `clear` mode (1–50). Seeded into `main` on first boot. */
|
|
95
|
-
contextReplyChainDepth: z.coerce.number().int().min(1).max(50).default(10),
|
|
96
|
-
|
|
97
|
-
// ─── Storage ────────────────────────────────────────────────────────
|
|
98
|
-
dataDir: z.string().default(".mercury"),
|
|
99
|
-
/** Max disk usage in MB for the agent's data directory. Unset = no enforcement (local/self-hosted). */
|
|
100
|
-
maxDiskMb: z.coerce.number().int().min(0).optional(),
|
|
101
|
-
|
|
102
|
-
// ─── Storage Lifecycle ─────────────────────────────────────────────
|
|
103
|
-
/** Days before inbox files are auto-deleted. */
|
|
104
|
-
inboxTtlDays: z.coerce.number().min(1).max(365).default(7),
|
|
105
|
-
/** Days before outbox files are auto-deleted. */
|
|
106
|
-
outboxTtlDays: z.coerce.number().min(1).max(365).default(3),
|
|
107
|
-
/** Interval in ms between storage cleanup runs. Default 1 hour. */
|
|
108
|
-
cleanupIntervalMs: z.coerce
|
|
109
|
-
.number()
|
|
110
|
-
.int()
|
|
111
|
-
.min(60_000)
|
|
112
|
-
.max(86_400_000)
|
|
113
|
-
.default(3_600_000),
|
|
114
|
-
authPath: z.string().optional(),
|
|
115
|
-
/** WhatsApp Baileys auth directory; default `<dataDir>/whatsapp-auth`. */
|
|
116
|
-
whatsappAuthDir: z.string().optional(),
|
|
117
|
-
|
|
118
|
-
// ─── Container / Agent ──────────────────────────────────────────────
|
|
119
|
-
agentContainerImage: z
|
|
120
|
-
.string()
|
|
121
|
-
.default("ghcr.io/avishai-tsabari/mercury-agent:latest"),
|
|
122
|
-
containerTimeoutMs: z.coerce
|
|
123
|
-
.number()
|
|
124
|
-
.int()
|
|
125
|
-
.min(10_000)
|
|
126
|
-
.max(60 * 60 * 1000)
|
|
127
|
-
.default(5 * 60 * 1000), // 5 minutes
|
|
128
|
-
/**
|
|
129
|
-
* OCI runtime for inner (pi) containers.
|
|
130
|
-
* - "runc" (default): standard Docker runtime; uses bubblewrap inside the container for sandboxing.
|
|
131
|
-
* - "runsc": gVisor runtime — intercepts syscalls at a user-space kernel boundary.
|
|
132
|
-
* Stronger isolation than bwrap; restores full Docker hardening (no SYS_ADMIN relaxation needed).
|
|
133
|
-
* Requires gVisor installed on the compute node (auto-installed by cloud-init on provisioned nodes).
|
|
134
|
-
*/
|
|
135
|
-
containerRuntime: z.enum(["runc", "runsc"]).default("runc"),
|
|
136
|
-
/**
|
|
137
|
-
* @deprecated Use MERCURY_CONTAINER_RUNTIME=runsc instead.
|
|
138
|
-
* When true, `docker run` uses looser outer sandbox so bubblewrap can nest (e.g. Docker Desktop).
|
|
139
|
-
* Ignored when containerRuntime is "runsc". See docs/container-lifecycle.md.
|
|
140
|
-
*/
|
|
141
|
-
containerBwrapDockerCompat: booleanFromEnv.default(false),
|
|
142
|
-
/**
|
|
143
|
-
* Docker network to attach inner (pi) containers to. When set, inner containers join this
|
|
144
|
-
* network and can reach the Mercury host container by its container name rather than via
|
|
145
|
-
* host.docker.internal. Required on Linux where host.docker.internal is not available.
|
|
146
|
-
* Set via MERCURY_CONTAINER_NETWORK (e.g. "mercury-net").
|
|
147
|
-
*/
|
|
148
|
-
containerNetwork: z.string().optional(),
|
|
149
|
-
/**
|
|
150
|
-
* Hostname (and optional port) that inner containers use to reach the Mercury host API.
|
|
151
|
-
* When set, overrides the default "host.docker.internal" in the API_URL passed to mrctl.
|
|
152
|
-
* Set via MERCURY_CONTAINER_API_HOST (e.g. "mercury-agent-<uuid>").
|
|
153
|
-
*/
|
|
154
|
-
containerApiHost: z.string().optional(),
|
|
155
|
-
maxConcurrency: z.coerce.number().int().min(1).max(32).default(2),
|
|
156
|
-
/**
|
|
157
|
-
* When true, Mercury uses `--system-prompt` instead of `--append-system-prompt` when invoking pi,
|
|
158
|
-
* making Mercury the sole author of the system prompt. The prompt includes accurate tool snippets
|
|
159
|
-
* and Mercury identity without any pi-specific references.
|
|
160
|
-
* Default: false (append mode, preserves existing behaviour).
|
|
161
|
-
*/
|
|
162
|
-
overridePiSystemPrompt: booleanFromEnv.default(false),
|
|
163
|
-
|
|
164
|
-
// ─── Rate Limiting ──────────────────────────────────────────────────
|
|
165
|
-
rateLimitPerUser: z.coerce.number().int().min(1).max(1000).default(10),
|
|
166
|
-
rateLimitWindowMs: z.coerce
|
|
167
|
-
.number()
|
|
168
|
-
.int()
|
|
169
|
-
.min(1000)
|
|
170
|
-
.max(60 * 60 * 1000)
|
|
171
|
-
.default(60 * 1000), // 1 minute
|
|
172
|
-
rateLimitDailyMember: z.coerce.number().int().min(0).max(10000).default(0),
|
|
173
|
-
rateLimitDailyAdmin: z.coerce.number().int().min(0).max(10000).default(0),
|
|
174
|
-
|
|
175
|
-
// ─── Server ─────────────────────────────────────────────────────────
|
|
176
|
-
port: z.coerce.number().int().min(1).max(65535).default(8787),
|
|
177
|
-
botUsername: z.string().default("mercury"),
|
|
178
|
-
|
|
179
|
-
// ─── Discord ────────────────────────────────────────────────────────
|
|
180
|
-
enableDiscord: booleanFromEnv.default(false),
|
|
181
|
-
discordGatewayDurationMs: z.coerce
|
|
182
|
-
.number()
|
|
183
|
-
.int()
|
|
184
|
-
.min(60_000)
|
|
185
|
-
.max(60 * 60 * 1000)
|
|
186
|
-
.default(10 * 60 * 1000),
|
|
187
|
-
discordGatewaySecret: z.string().optional(),
|
|
188
|
-
|
|
189
|
-
// ─── Slack ──────────────────────────────────────────────────────────
|
|
190
|
-
enableSlack: booleanFromEnv.default(false),
|
|
191
|
-
|
|
192
|
-
// ─── Teams ───────────────────────────────────────────────────────────
|
|
193
|
-
enableTeams: booleanFromEnv.default(false),
|
|
194
|
-
|
|
195
|
-
// ─── WhatsApp ───────────────────────────────────────────────────────
|
|
196
|
-
enableWhatsApp: booleanFromEnv.default(false),
|
|
197
|
-
|
|
198
|
-
// ─── Telegram ───────────────────────────────────────────────────────
|
|
199
|
-
enableTelegram: booleanFromEnv.default(false),
|
|
200
|
-
/** When true, convert Markdown to Telegram HTML for formatted replies. */
|
|
201
|
-
telegramFormatEnabled: booleanFromEnv.default(true),
|
|
202
|
-
|
|
203
|
-
// ─── Media Handling ─────────────────────────────────────────────────
|
|
204
|
-
mediaEnabled: booleanFromEnv.default(true),
|
|
205
|
-
mediaMaxSizeMb: z.coerce.number().min(1).max(100).default(10),
|
|
206
|
-
|
|
207
|
-
// ─── Permissions ────────────────────────────────────────────────────
|
|
208
|
-
admins: z.string().default(""),
|
|
209
|
-
|
|
210
|
-
// ─── Applicative Profile ────────────────────────────────────────────
|
|
211
|
-
/**
|
|
212
|
-
* Name of the active applicative profile (informational at runtime; the
|
|
213
|
-
* authoritative activation is persisted to `.mercury/active-profile.json`
|
|
214
|
-
* by `applyProfile`). Env-only.
|
|
215
|
-
*/
|
|
216
|
-
profile: z.string().optional(),
|
|
217
|
-
|
|
218
|
-
// ─── Security ─────────────────────────────────────────────────────
|
|
219
|
-
/** Shared secret for API authentication. Required for /api/* routes. */
|
|
220
|
-
apiSecret: z.string().optional(),
|
|
221
|
-
/**
|
|
222
|
-
* Host-only HMAC key for signing per-turn caller tokens. Never injected into
|
|
223
|
-
* containers. When unset, an ephemeral random key is generated per host
|
|
224
|
-
* process (fine for single-process deployments). Only set this when token
|
|
225
|
-
* minting and verification run in separate processes.
|
|
226
|
-
*/
|
|
227
|
-
callerTokenKey: z.string().optional(),
|
|
228
|
-
/** Optional API key for the /chat endpoint. When unset, /chat is open (for local use). */
|
|
229
|
-
chatApiKey: z.string().optional(),
|
|
230
|
-
/**
|
|
231
|
-
* URL of the Mercury Cloud Console managing this agent (e.g. "https://console.mercury.app").
|
|
232
|
-
* When set, the dashboard keys page redirects users to the Console instead of allowing
|
|
233
|
-
* direct key edits — the Console is the single source of truth for API keys.
|
|
234
|
-
* Env-only; not settable from mercury.yaml.
|
|
235
|
-
*/
|
|
236
|
-
consoleUrl: z.string().url().optional(),
|
|
237
|
-
/** User ID in the Mercury Cloud Console — used for per-message quota checks. Env-only. */
|
|
238
|
-
consoleUserId: z.string().optional(),
|
|
239
|
-
/** Shared secret for calling console internal API endpoints. Env-only. */
|
|
240
|
-
consoleInternalSecret: z.string().optional(),
|
|
241
|
-
|
|
242
|
-
// ─── Scheduling ─────────────────────────────────────────────────────
|
|
243
|
-
/** IANA timezone used when a scheduled task is created without an explicit --timezone flag (e.g. "Asia/Jerusalem"). */
|
|
244
|
-
defaultTimezone: z.string().optional(),
|
|
245
|
-
|
|
246
|
-
// ─── TradeStation (host order API) ────────────────────────────────
|
|
247
|
-
/**
|
|
248
|
-
* When false (default), POST /api/tradestation/orders rejects non-SIM accounts.
|
|
249
|
-
* Set true only when you intentionally allow live brokerage orders from the assistant flow.
|
|
250
|
-
*/
|
|
251
|
-
tsAllowLiveOrders: booleanFromEnv.default(false),
|
|
252
|
-
|
|
253
|
-
// ─── Cloud TTS (host-only; /api/tts, optional voice-synth extension) ───
|
|
254
|
-
/** `google` | `azure` | `auto` — auto picks Google if credentials file set, else Azure if key+region set. */
|
|
255
|
-
ttsProvider: z.enum(["google", "azure", "auto"]).default("auto"),
|
|
256
|
-
/** Azure Speech resource key (secret; env-only). */
|
|
257
|
-
azureSpeechKey: z.string().optional(),
|
|
258
|
-
/** Azure region, e.g. `eastus`. */
|
|
259
|
-
azureSpeechRegion: z.string().optional(),
|
|
260
|
-
/**
|
|
261
|
-
* Path to GCP service account JSON for Text-to-Speech.
|
|
262
|
-
* Also accepts standard `GOOGLE_APPLICATION_CREDENTIALS` via mergeRawMercuryConfig.
|
|
263
|
-
*/
|
|
264
|
-
googleApplicationCredentials: z.string().optional(),
|
|
265
|
-
/** Max input characters per /api/tts request (clamped 500–10000). */
|
|
266
|
-
ttsMaxChars: z.coerce.number().int().min(500).max(10_000).default(5000),
|
|
267
|
-
|
|
268
|
-
// ─── DM Auto-Space ─────────────────────────────────────────────────
|
|
269
|
-
dmAutoSpaceEnabled: booleanFromEnv.default(false),
|
|
270
|
-
dmAutoSpaceAdminIds: z.string().default(""),
|
|
271
|
-
dmAutoSpaceDefaultSystemPrompt: z.string().default(""),
|
|
272
|
-
dmAutoSpaceDefaultMemberPermissions: z.string().default("prompt,prefs.get"),
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
export type AppConfig = z.infer<typeof schema> & {
|
|
276
|
-
/** Derived paths from dataDir */
|
|
277
|
-
dbPath: string;
|
|
278
|
-
globalDir: string;
|
|
279
|
-
spacesDir: string;
|
|
280
|
-
whatsappAuthDir: string;
|
|
281
|
-
/** Ordered model legs (primary first), max 20. */
|
|
282
|
-
resolvedModelChain: ModelLeg[];
|
|
283
|
-
/** Parsed MERCURY_MODEL_CAPABILITIES override, if valid. */
|
|
284
|
-
parsedModelCapabilitiesEnv: ModelCapabilities | null;
|
|
285
|
-
/** Capabilities per chain leg (same order as resolvedModelChain). */
|
|
286
|
-
resolvedModelChainCapabilities: ModelCapabilities[];
|
|
287
|
-
/** Effective budget after clamping to container timeout. */
|
|
288
|
-
effectiveModelChainBudgetMs: number;
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
export function loadConfig(): AppConfig {
|
|
292
|
-
const raw = mergeRawMercuryConfig(process.env);
|
|
293
|
-
const base = schema.parse(raw);
|
|
294
|
-
|
|
295
|
-
const dataDir = base.dataDir;
|
|
296
|
-
|
|
297
|
-
const resolvedModelChain = resolveModelChain({
|
|
298
|
-
modelChain: base.modelChain,
|
|
299
|
-
modelProvider: base.modelProvider,
|
|
300
|
-
model: base.model,
|
|
301
|
-
modelFallbackProvider: base.modelFallbackProvider,
|
|
302
|
-
modelFallback: base.modelFallback,
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
const dataDirAbsolute = resolveProjectPath(base.dataDir);
|
|
306
|
-
const parsedModelCapabilitiesEnv = parseModelCapabilitiesEnv(
|
|
307
|
-
base.modelCapabilitiesEnv,
|
|
308
|
-
);
|
|
309
|
-
const { chainCaps: resolvedModelChainCapabilities } =
|
|
310
|
-
resolveModelChainCapabilities(
|
|
311
|
-
resolvedModelChain,
|
|
312
|
-
dataDirAbsolute,
|
|
313
|
-
parsedModelCapabilitiesEnv,
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
const slackMs = 10_000;
|
|
317
|
-
const effectiveModelChainBudgetMs = Math.min(
|
|
318
|
-
base.modelChainBudgetMs,
|
|
319
|
-
Math.max(5000, base.containerTimeoutMs - slackMs),
|
|
320
|
-
);
|
|
321
|
-
|
|
322
|
-
return {
|
|
323
|
-
...base,
|
|
324
|
-
dbPath: path.join(dataDir, "state.db"),
|
|
325
|
-
globalDir: path.join(dataDir, "global"),
|
|
326
|
-
spacesDir: path.join(dataDir, "spaces"),
|
|
327
|
-
whatsappAuthDir:
|
|
328
|
-
base.whatsappAuthDir ?? path.join(dataDir, "whatsapp-auth"),
|
|
329
|
-
resolvedModelChain,
|
|
330
|
-
parsedModelCapabilitiesEnv,
|
|
331
|
-
resolvedModelChainCapabilities,
|
|
332
|
-
effectiveModelChainBudgetMs,
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
export function resolveProjectPath(p: string): string {
|
|
337
|
-
if (path.isAbsolute(p)) return p;
|
|
338
|
-
return path.join(process.cwd(), p);
|
|
339
|
-
}
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
type ModelCapabilities,
|
|
5
|
+
parseModelCapabilitiesEnv,
|
|
6
|
+
resolveModelChainCapabilities,
|
|
7
|
+
} from "./agent/model-capabilities.js";
|
|
8
|
+
import { mergeRawMercuryConfig } from "./config-file.js";
|
|
9
|
+
import { parseModelLegsArray } from "./config-model-chain.js";
|
|
10
|
+
|
|
11
|
+
/** One model leg in the ordered fallback chain (primary first). */
|
|
12
|
+
export type ModelLeg = { provider: string; model: string };
|
|
13
|
+
|
|
14
|
+
function parseModelChainJson(raw: string): ModelLeg[] {
|
|
15
|
+
let parsed: unknown;
|
|
16
|
+
try {
|
|
17
|
+
parsed = JSON.parse(raw);
|
|
18
|
+
} catch {
|
|
19
|
+
throw new Error("MERCURY_MODEL_CHAIN must be valid JSON array");
|
|
20
|
+
}
|
|
21
|
+
return parseModelLegsArray(parsed, "MERCURY_MODEL_CHAIN");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function resolveModelChain(base: {
|
|
25
|
+
modelChain: string | undefined;
|
|
26
|
+
modelProvider: string;
|
|
27
|
+
model: string;
|
|
28
|
+
modelFallbackProvider: string | undefined;
|
|
29
|
+
modelFallback: string | undefined;
|
|
30
|
+
}): ModelLeg[] {
|
|
31
|
+
const trimmed = base.modelChain?.trim();
|
|
32
|
+
if (trimmed) {
|
|
33
|
+
return parseModelChainJson(trimmed);
|
|
34
|
+
}
|
|
35
|
+
const legs: ModelLeg[] = [
|
|
36
|
+
{ provider: base.modelProvider, model: base.model },
|
|
37
|
+
];
|
|
38
|
+
const fp = base.modelFallbackProvider?.trim();
|
|
39
|
+
const fm = base.modelFallback?.trim();
|
|
40
|
+
if (fp && fm) {
|
|
41
|
+
legs.push({ provider: fp, model: fm });
|
|
42
|
+
}
|
|
43
|
+
return legs;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Parse boolean from env var strings — case-insensitive "true"/"1" → true, everything else → false */
|
|
47
|
+
const booleanFromEnv = z.union([z.boolean(), z.string()]).transform((val) => {
|
|
48
|
+
if (typeof val === "boolean") return val;
|
|
49
|
+
const lower = val.toLowerCase();
|
|
50
|
+
return lower === "true" || lower === "1";
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const schema = z.object({
|
|
54
|
+
// ─── API Key Mode ───────────────────────────────────────────────────
|
|
55
|
+
apiKeyMode: z.enum(["platform", "byok"]).default("platform"),
|
|
56
|
+
|
|
57
|
+
// ─── Logging ────────────────────────────────────────────────────────
|
|
58
|
+
logLevel: z
|
|
59
|
+
.enum(["debug", "info", "warn", "error", "silent"])
|
|
60
|
+
.default("info"),
|
|
61
|
+
logFormat: z.enum(["text", "json"]).default("text"),
|
|
62
|
+
|
|
63
|
+
// ─── AI Model ───────────────────────────────────────────────────────
|
|
64
|
+
modelProvider: z.string().default("anthropic"),
|
|
65
|
+
model: z.string().default("claude-opus-4-8"),
|
|
66
|
+
modelFallbackProvider: z.string().optional(),
|
|
67
|
+
modelFallback: z.string().optional(),
|
|
68
|
+
/** JSON array of `{ provider, model }`. When set, overrides legacy primary+fallback pair. */
|
|
69
|
+
modelChain: z.string().optional(),
|
|
70
|
+
/** Extra attempts after the first failure on the same leg (retryable errors only). Default 2 => 3 tries max per leg. */
|
|
71
|
+
modelMaxRetriesPerLeg: z.coerce.number().int().min(0).max(5).default(2),
|
|
72
|
+
/** Wall-clock budget for the whole chain (ms). Clamped below container timeout. Default 120s. */
|
|
73
|
+
modelChainBudgetMs: z.coerce
|
|
74
|
+
.number()
|
|
75
|
+
.int()
|
|
76
|
+
.min(5000)
|
|
77
|
+
.max(55 * 60 * 1000)
|
|
78
|
+
.default(120_000),
|
|
79
|
+
/**
|
|
80
|
+
* Optional JSON object overriding model capabilities for all chain legs, e.g.
|
|
81
|
+
* `{"tools":false,"vision":true}`. Highest priority over YAML and built-in map.
|
|
82
|
+
*/
|
|
83
|
+
modelCapabilitiesEnv: z.string().optional(),
|
|
84
|
+
|
|
85
|
+
// ─── Trigger Behavior ───────────────────────────────────────────────
|
|
86
|
+
triggerPatterns: z.string().default("@Mercury,Mercury"),
|
|
87
|
+
triggerMatch: z.string().default("mention"),
|
|
88
|
+
|
|
89
|
+
// ─── Context Behavior ───────────────────────────────────────────────
|
|
90
|
+
/** Default context mode seeded into the `main` space on first boot. */
|
|
91
|
+
contextMode: z.enum(["clear", "context"]).default("context"),
|
|
92
|
+
/** Default sliding-window turn count for `context` mode (1–50). Seeded into `main` on first boot. */
|
|
93
|
+
contextWindowSize: z.coerce.number().int().min(1).max(50).default(10),
|
|
94
|
+
/** Default reply-chain depth for `clear` mode (1–50). Seeded into `main` on first boot. */
|
|
95
|
+
contextReplyChainDepth: z.coerce.number().int().min(1).max(50).default(10),
|
|
96
|
+
|
|
97
|
+
// ─── Storage ────────────────────────────────────────────────────────
|
|
98
|
+
dataDir: z.string().default(".mercury"),
|
|
99
|
+
/** Max disk usage in MB for the agent's data directory. Unset = no enforcement (local/self-hosted). */
|
|
100
|
+
maxDiskMb: z.coerce.number().int().min(0).optional(),
|
|
101
|
+
|
|
102
|
+
// ─── Storage Lifecycle ─────────────────────────────────────────────
|
|
103
|
+
/** Days before inbox files are auto-deleted. */
|
|
104
|
+
inboxTtlDays: z.coerce.number().min(1).max(365).default(7),
|
|
105
|
+
/** Days before outbox files are auto-deleted. */
|
|
106
|
+
outboxTtlDays: z.coerce.number().min(1).max(365).default(3),
|
|
107
|
+
/** Interval in ms between storage cleanup runs. Default 1 hour. */
|
|
108
|
+
cleanupIntervalMs: z.coerce
|
|
109
|
+
.number()
|
|
110
|
+
.int()
|
|
111
|
+
.min(60_000)
|
|
112
|
+
.max(86_400_000)
|
|
113
|
+
.default(3_600_000),
|
|
114
|
+
authPath: z.string().optional(),
|
|
115
|
+
/** WhatsApp Baileys auth directory; default `<dataDir>/whatsapp-auth`. */
|
|
116
|
+
whatsappAuthDir: z.string().optional(),
|
|
117
|
+
|
|
118
|
+
// ─── Container / Agent ──────────────────────────────────────────────
|
|
119
|
+
agentContainerImage: z
|
|
120
|
+
.string()
|
|
121
|
+
.default("ghcr.io/avishai-tsabari/mercury-agent:latest"),
|
|
122
|
+
containerTimeoutMs: z.coerce
|
|
123
|
+
.number()
|
|
124
|
+
.int()
|
|
125
|
+
.min(10_000)
|
|
126
|
+
.max(60 * 60 * 1000)
|
|
127
|
+
.default(5 * 60 * 1000), // 5 minutes
|
|
128
|
+
/**
|
|
129
|
+
* OCI runtime for inner (pi) containers.
|
|
130
|
+
* - "runc" (default): standard Docker runtime; uses bubblewrap inside the container for sandboxing.
|
|
131
|
+
* - "runsc": gVisor runtime — intercepts syscalls at a user-space kernel boundary.
|
|
132
|
+
* Stronger isolation than bwrap; restores full Docker hardening (no SYS_ADMIN relaxation needed).
|
|
133
|
+
* Requires gVisor installed on the compute node (auto-installed by cloud-init on provisioned nodes).
|
|
134
|
+
*/
|
|
135
|
+
containerRuntime: z.enum(["runc", "runsc"]).default("runc"),
|
|
136
|
+
/**
|
|
137
|
+
* @deprecated Use MERCURY_CONTAINER_RUNTIME=runsc instead.
|
|
138
|
+
* When true, `docker run` uses looser outer sandbox so bubblewrap can nest (e.g. Docker Desktop).
|
|
139
|
+
* Ignored when containerRuntime is "runsc". See docs/container-lifecycle.md.
|
|
140
|
+
*/
|
|
141
|
+
containerBwrapDockerCompat: booleanFromEnv.default(false),
|
|
142
|
+
/**
|
|
143
|
+
* Docker network to attach inner (pi) containers to. When set, inner containers join this
|
|
144
|
+
* network and can reach the Mercury host container by its container name rather than via
|
|
145
|
+
* host.docker.internal. Required on Linux where host.docker.internal is not available.
|
|
146
|
+
* Set via MERCURY_CONTAINER_NETWORK (e.g. "mercury-net").
|
|
147
|
+
*/
|
|
148
|
+
containerNetwork: z.string().optional(),
|
|
149
|
+
/**
|
|
150
|
+
* Hostname (and optional port) that inner containers use to reach the Mercury host API.
|
|
151
|
+
* When set, overrides the default "host.docker.internal" in the API_URL passed to mrctl.
|
|
152
|
+
* Set via MERCURY_CONTAINER_API_HOST (e.g. "mercury-agent-<uuid>").
|
|
153
|
+
*/
|
|
154
|
+
containerApiHost: z.string().optional(),
|
|
155
|
+
maxConcurrency: z.coerce.number().int().min(1).max(32).default(2),
|
|
156
|
+
/**
|
|
157
|
+
* When true, Mercury uses `--system-prompt` instead of `--append-system-prompt` when invoking pi,
|
|
158
|
+
* making Mercury the sole author of the system prompt. The prompt includes accurate tool snippets
|
|
159
|
+
* and Mercury identity without any pi-specific references.
|
|
160
|
+
* Default: false (append mode, preserves existing behaviour).
|
|
161
|
+
*/
|
|
162
|
+
overridePiSystemPrompt: booleanFromEnv.default(false),
|
|
163
|
+
|
|
164
|
+
// ─── Rate Limiting ──────────────────────────────────────────────────
|
|
165
|
+
rateLimitPerUser: z.coerce.number().int().min(1).max(1000).default(10),
|
|
166
|
+
rateLimitWindowMs: z.coerce
|
|
167
|
+
.number()
|
|
168
|
+
.int()
|
|
169
|
+
.min(1000)
|
|
170
|
+
.max(60 * 60 * 1000)
|
|
171
|
+
.default(60 * 1000), // 1 minute
|
|
172
|
+
rateLimitDailyMember: z.coerce.number().int().min(0).max(10000).default(0),
|
|
173
|
+
rateLimitDailyAdmin: z.coerce.number().int().min(0).max(10000).default(0),
|
|
174
|
+
|
|
175
|
+
// ─── Server ─────────────────────────────────────────────────────────
|
|
176
|
+
port: z.coerce.number().int().min(1).max(65535).default(8787),
|
|
177
|
+
botUsername: z.string().default("mercury"),
|
|
178
|
+
|
|
179
|
+
// ─── Discord ────────────────────────────────────────────────────────
|
|
180
|
+
enableDiscord: booleanFromEnv.default(false),
|
|
181
|
+
discordGatewayDurationMs: z.coerce
|
|
182
|
+
.number()
|
|
183
|
+
.int()
|
|
184
|
+
.min(60_000)
|
|
185
|
+
.max(60 * 60 * 1000)
|
|
186
|
+
.default(10 * 60 * 1000),
|
|
187
|
+
discordGatewaySecret: z.string().optional(),
|
|
188
|
+
|
|
189
|
+
// ─── Slack ──────────────────────────────────────────────────────────
|
|
190
|
+
enableSlack: booleanFromEnv.default(false),
|
|
191
|
+
|
|
192
|
+
// ─── Teams ───────────────────────────────────────────────────────────
|
|
193
|
+
enableTeams: booleanFromEnv.default(false),
|
|
194
|
+
|
|
195
|
+
// ─── WhatsApp ───────────────────────────────────────────────────────
|
|
196
|
+
enableWhatsApp: booleanFromEnv.default(false),
|
|
197
|
+
|
|
198
|
+
// ─── Telegram ───────────────────────────────────────────────────────
|
|
199
|
+
enableTelegram: booleanFromEnv.default(false),
|
|
200
|
+
/** When true, convert Markdown to Telegram HTML for formatted replies. */
|
|
201
|
+
telegramFormatEnabled: booleanFromEnv.default(true),
|
|
202
|
+
|
|
203
|
+
// ─── Media Handling ─────────────────────────────────────────────────
|
|
204
|
+
mediaEnabled: booleanFromEnv.default(true),
|
|
205
|
+
mediaMaxSizeMb: z.coerce.number().min(1).max(100).default(10),
|
|
206
|
+
|
|
207
|
+
// ─── Permissions ────────────────────────────────────────────────────
|
|
208
|
+
admins: z.string().default(""),
|
|
209
|
+
|
|
210
|
+
// ─── Applicative Profile ────────────────────────────────────────────
|
|
211
|
+
/**
|
|
212
|
+
* Name of the active applicative profile (informational at runtime; the
|
|
213
|
+
* authoritative activation is persisted to `.mercury/active-profile.json`
|
|
214
|
+
* by `applyProfile`). Env-only.
|
|
215
|
+
*/
|
|
216
|
+
profile: z.string().optional(),
|
|
217
|
+
|
|
218
|
+
// ─── Security ─────────────────────────────────────────────────────
|
|
219
|
+
/** Shared secret for API authentication. Required for /api/* routes. */
|
|
220
|
+
apiSecret: z.string().optional(),
|
|
221
|
+
/**
|
|
222
|
+
* Host-only HMAC key for signing per-turn caller tokens. Never injected into
|
|
223
|
+
* containers. When unset, an ephemeral random key is generated per host
|
|
224
|
+
* process (fine for single-process deployments). Only set this when token
|
|
225
|
+
* minting and verification run in separate processes.
|
|
226
|
+
*/
|
|
227
|
+
callerTokenKey: z.string().optional(),
|
|
228
|
+
/** Optional API key for the /chat endpoint. When unset, /chat is open (for local use). */
|
|
229
|
+
chatApiKey: z.string().optional(),
|
|
230
|
+
/**
|
|
231
|
+
* URL of the Mercury Cloud Console managing this agent (e.g. "https://console.mercury.app").
|
|
232
|
+
* When set, the dashboard keys page redirects users to the Console instead of allowing
|
|
233
|
+
* direct key edits — the Console is the single source of truth for API keys.
|
|
234
|
+
* Env-only; not settable from mercury.yaml.
|
|
235
|
+
*/
|
|
236
|
+
consoleUrl: z.string().url().optional(),
|
|
237
|
+
/** User ID in the Mercury Cloud Console — used for per-message quota checks. Env-only. */
|
|
238
|
+
consoleUserId: z.string().optional(),
|
|
239
|
+
/** Shared secret for calling console internal API endpoints. Env-only. */
|
|
240
|
+
consoleInternalSecret: z.string().optional(),
|
|
241
|
+
|
|
242
|
+
// ─── Scheduling ─────────────────────────────────────────────────────
|
|
243
|
+
/** IANA timezone used when a scheduled task is created without an explicit --timezone flag (e.g. "Asia/Jerusalem"). */
|
|
244
|
+
defaultTimezone: z.string().optional(),
|
|
245
|
+
|
|
246
|
+
// ─── TradeStation (host order API) ────────────────────────────────
|
|
247
|
+
/**
|
|
248
|
+
* When false (default), POST /api/tradestation/orders rejects non-SIM accounts.
|
|
249
|
+
* Set true only when you intentionally allow live brokerage orders from the assistant flow.
|
|
250
|
+
*/
|
|
251
|
+
tsAllowLiveOrders: booleanFromEnv.default(false),
|
|
252
|
+
|
|
253
|
+
// ─── Cloud TTS (host-only; /api/tts, optional voice-synth extension) ───
|
|
254
|
+
/** `google` | `azure` | `auto` — auto picks Google if credentials file set, else Azure if key+region set. */
|
|
255
|
+
ttsProvider: z.enum(["google", "azure", "auto"]).default("auto"),
|
|
256
|
+
/** Azure Speech resource key (secret; env-only). */
|
|
257
|
+
azureSpeechKey: z.string().optional(),
|
|
258
|
+
/** Azure region, e.g. `eastus`. */
|
|
259
|
+
azureSpeechRegion: z.string().optional(),
|
|
260
|
+
/**
|
|
261
|
+
* Path to GCP service account JSON for Text-to-Speech.
|
|
262
|
+
* Also accepts standard `GOOGLE_APPLICATION_CREDENTIALS` via mergeRawMercuryConfig.
|
|
263
|
+
*/
|
|
264
|
+
googleApplicationCredentials: z.string().optional(),
|
|
265
|
+
/** Max input characters per /api/tts request (clamped 500–10000). */
|
|
266
|
+
ttsMaxChars: z.coerce.number().int().min(500).max(10_000).default(5000),
|
|
267
|
+
|
|
268
|
+
// ─── DM Auto-Space ─────────────────────────────────────────────────
|
|
269
|
+
dmAutoSpaceEnabled: booleanFromEnv.default(false),
|
|
270
|
+
dmAutoSpaceAdminIds: z.string().default(""),
|
|
271
|
+
dmAutoSpaceDefaultSystemPrompt: z.string().default(""),
|
|
272
|
+
dmAutoSpaceDefaultMemberPermissions: z.string().default("prompt,prefs.get"),
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
export type AppConfig = z.infer<typeof schema> & {
|
|
276
|
+
/** Derived paths from dataDir */
|
|
277
|
+
dbPath: string;
|
|
278
|
+
globalDir: string;
|
|
279
|
+
spacesDir: string;
|
|
280
|
+
whatsappAuthDir: string;
|
|
281
|
+
/** Ordered model legs (primary first), max 20. */
|
|
282
|
+
resolvedModelChain: ModelLeg[];
|
|
283
|
+
/** Parsed MERCURY_MODEL_CAPABILITIES override, if valid. */
|
|
284
|
+
parsedModelCapabilitiesEnv: ModelCapabilities | null;
|
|
285
|
+
/** Capabilities per chain leg (same order as resolvedModelChain). */
|
|
286
|
+
resolvedModelChainCapabilities: ModelCapabilities[];
|
|
287
|
+
/** Effective budget after clamping to container timeout. */
|
|
288
|
+
effectiveModelChainBudgetMs: number;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
export function loadConfig(): AppConfig {
|
|
292
|
+
const raw = mergeRawMercuryConfig(process.env);
|
|
293
|
+
const base = schema.parse(raw);
|
|
294
|
+
|
|
295
|
+
const dataDir = base.dataDir;
|
|
296
|
+
|
|
297
|
+
const resolvedModelChain = resolveModelChain({
|
|
298
|
+
modelChain: base.modelChain,
|
|
299
|
+
modelProvider: base.modelProvider,
|
|
300
|
+
model: base.model,
|
|
301
|
+
modelFallbackProvider: base.modelFallbackProvider,
|
|
302
|
+
modelFallback: base.modelFallback,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const dataDirAbsolute = resolveProjectPath(base.dataDir);
|
|
306
|
+
const parsedModelCapabilitiesEnv = parseModelCapabilitiesEnv(
|
|
307
|
+
base.modelCapabilitiesEnv,
|
|
308
|
+
);
|
|
309
|
+
const { chainCaps: resolvedModelChainCapabilities } =
|
|
310
|
+
resolveModelChainCapabilities(
|
|
311
|
+
resolvedModelChain,
|
|
312
|
+
dataDirAbsolute,
|
|
313
|
+
parsedModelCapabilitiesEnv,
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
const slackMs = 10_000;
|
|
317
|
+
const effectiveModelChainBudgetMs = Math.min(
|
|
318
|
+
base.modelChainBudgetMs,
|
|
319
|
+
Math.max(5000, base.containerTimeoutMs - slackMs),
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
...base,
|
|
324
|
+
dbPath: path.join(dataDir, "state.db"),
|
|
325
|
+
globalDir: path.join(dataDir, "global"),
|
|
326
|
+
spacesDir: path.join(dataDir, "spaces"),
|
|
327
|
+
whatsappAuthDir:
|
|
328
|
+
base.whatsappAuthDir ?? path.join(dataDir, "whatsapp-auth"),
|
|
329
|
+
resolvedModelChain,
|
|
330
|
+
parsedModelCapabilitiesEnv,
|
|
331
|
+
resolvedModelChainCapabilities,
|
|
332
|
+
effectiveModelChainBudgetMs,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function resolveProjectPath(p: string): string {
|
|
337
|
+
if (path.isAbsolute(p)) return p;
|
|
338
|
+
return path.join(process.cwd(), p);
|
|
339
|
+
}
|