mercury-agent 0.4.6 → 0.4.8
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/docs/auth/dashboard.md +28 -28
- package/examples/extensions/voice-synth/index.ts +94 -94
- package/package.json +1 -1
- package/src/adapters/whatsapp.ts +635 -629
- package/src/agent/model-capabilities.ts +231 -231
- package/src/bridges/discord.ts +178 -171
- package/src/bridges/slack.ts +179 -177
- package/src/bridges/teams.ts +162 -160
- package/src/bridges/telegram.ts +579 -571
- package/src/cli/mercury.ts +2536 -2531
- package/src/cli/whatsapp-auth.ts +263 -260
- package/src/config.ts +316 -316
- package/src/core/permissions.ts +196 -192
- package/src/core/router.ts +191 -191
- package/src/core/routes/chat.ts +175 -172
- package/src/core/routes/dashboard.ts +2491 -2491
- package/src/core/routes/messages.ts +37 -37
- package/src/core/routes/mutes.ts +95 -89
- package/src/core/routes/roles.ts +135 -125
- package/src/core/runtime.ts +1140 -1140
- package/src/core/task-scheduler.ts +139 -132
- package/src/extensions/catalog.ts +117 -117
- package/src/extensions/hooks.ts +161 -156
- package/src/extensions/installer.ts +306 -306
- package/src/extensions/loader.ts +271 -271
- package/src/extensions/permission-guard.ts +52 -52
- package/src/server.ts +391 -391
- package/src/storage/db.ts +1625 -1624
- package/src/storage/pi-auth.ts +95 -95
- package/src/tts/azure.ts +52 -52
- package/src/tts/synthesize.ts +133 -133
|
@@ -1,231 +1,231 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Model capability flags — used to adapt prompts, pi tool flags, and skill installation.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import { getModels, type KnownProvider } from "@earendil-works/pi-ai";
|
|
8
|
-
import { parse as parseYaml } from "yaml";
|
|
9
|
-
import { z } from "zod";
|
|
10
|
-
import type { ModelLeg } from "../config.js";
|
|
11
|
-
import {
|
|
12
|
-
DEFAULT_CAPABILITIES,
|
|
13
|
-
type ModelCapabilities,
|
|
14
|
-
type ModelCapabilityKey,
|
|
15
|
-
} from "./model-capabilities-core.js";
|
|
16
|
-
|
|
17
|
-
export type {
|
|
18
|
-
ModelCapabilities,
|
|
19
|
-
ModelCapabilityKey,
|
|
20
|
-
} from "./model-capabilities-core.js";
|
|
21
|
-
export { DEFAULT_CAPABILITIES } from "./model-capabilities-core.js";
|
|
22
|
-
|
|
23
|
-
export type CapabilityResolveSource = "env" | "yaml" | "builtin" | "default";
|
|
24
|
-
|
|
25
|
-
export type ResolvedModelCapabilities = {
|
|
26
|
-
capabilities: ModelCapabilities;
|
|
27
|
-
source: CapabilityResolveSource;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
function mergePartialCapabilities(
|
|
31
|
-
partial: Partial<Record<ModelCapabilityKey, unknown>>,
|
|
32
|
-
): ModelCapabilities {
|
|
33
|
-
const out = { ...DEFAULT_CAPABILITIES };
|
|
34
|
-
for (const k of Object.keys(partial) as ModelCapabilityKey[]) {
|
|
35
|
-
const v = partial[k];
|
|
36
|
-
if (typeof v === "boolean") out[k] = v;
|
|
37
|
-
}
|
|
38
|
-
return out;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const partialCapsSchema = z.object({
|
|
42
|
-
tools: z.boolean().optional(),
|
|
43
|
-
vision: z.boolean().optional(),
|
|
44
|
-
audio_input: z.boolean().optional(),
|
|
45
|
-
audio_output: z.boolean().optional(),
|
|
46
|
-
extended_thinking: z.boolean().optional(),
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const yamlFileSchema = z.object({
|
|
50
|
-
models: z.record(z.string(), partialCapsSchema),
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
export type UserModelCapabilitiesMap = Map<string, ModelCapabilities>;
|
|
54
|
-
|
|
55
|
-
/** Path to optional user overrides: `<dataDir>/model-capabilities.yaml` */
|
|
56
|
-
export function modelCapabilitiesYamlPath(dataDirAbsolute: string): string {
|
|
57
|
-
return path.join(dataDirAbsolute, "model-capabilities.yaml");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Load `.mercury/model-capabilities.yaml` (under resolved data dir).
|
|
62
|
-
* Returns null if missing or invalid (callers may log).
|
|
63
|
-
*/
|
|
64
|
-
export function loadUserModelCapabilitiesMap(
|
|
65
|
-
dataDirAbsolute: string,
|
|
66
|
-
): UserModelCapabilitiesMap | null {
|
|
67
|
-
const yamlPath = modelCapabilitiesYamlPath(dataDirAbsolute);
|
|
68
|
-
if (!existsSync(yamlPath)) return null;
|
|
69
|
-
try {
|
|
70
|
-
const raw = readFileSync(yamlPath, "utf8");
|
|
71
|
-
const doc = parseYaml(raw) as unknown;
|
|
72
|
-
const parsed = yamlFileSchema.safeParse(doc);
|
|
73
|
-
if (!parsed.success) return null;
|
|
74
|
-
const map = new Map<string, ModelCapabilities>();
|
|
75
|
-
for (const [modelId, partial] of Object.entries(parsed.data.models)) {
|
|
76
|
-
map.set(modelId.trim(), mergePartialCapabilities(partial));
|
|
77
|
-
}
|
|
78
|
-
return map;
|
|
79
|
-
} catch {
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Look up capabilities for a model using pi's MODELS registry as the source of truth.
|
|
86
|
-
* Derives: vision (image in input), audio_input (audio in input), extended_thinking (reasoning).
|
|
87
|
-
* tools defaults to true — pi has no tools field; all pi-known models support tool use.
|
|
88
|
-
* audio_output is not tracked by pi; always false.
|
|
89
|
-
*/
|
|
90
|
-
function matchBuiltinCapabilities(
|
|
91
|
-
provider: string,
|
|
92
|
-
modelId: string,
|
|
93
|
-
): ModelCapabilities | null {
|
|
94
|
-
// Cast to KnownProvider — getModels returns [] for unrecognised providers at runtime
|
|
95
|
-
const model = getModels(provider as KnownProvider).find(
|
|
96
|
-
(m) => m.id === modelId,
|
|
97
|
-
);
|
|
98
|
-
if (!model) return null;
|
|
99
|
-
return {
|
|
100
|
-
tools: true,
|
|
101
|
-
vision: model.input.includes("image"),
|
|
102
|
-
audio_input: false, // pi types input as ("text"|"image")[]; no audio models exist yet
|
|
103
|
-
audio_output: false,
|
|
104
|
-
extended_thinking: model.reasoning,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** Parse MERCURY_MODEL_CAPABILITIES JSON; returns null if unset or invalid. */
|
|
109
|
-
export function parseModelCapabilitiesEnv(
|
|
110
|
-
raw: string | undefined,
|
|
111
|
-
): ModelCapabilities | null {
|
|
112
|
-
const trimmed = raw?.trim();
|
|
113
|
-
if (!trimmed) return null;
|
|
114
|
-
try {
|
|
115
|
-
const json = JSON.parse(trimmed) as unknown;
|
|
116
|
-
const r = partialCapsSchema.safeParse(json);
|
|
117
|
-
if (!r.success) return null;
|
|
118
|
-
return mergePartialCapabilities(r.data);
|
|
119
|
-
} catch {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Resolve capabilities for one model id (single leg).
|
|
126
|
-
* Priority: env global override → YAML exact model key → pi MODELS lookup → default.
|
|
127
|
-
*/
|
|
128
|
-
export function resolveModelCapabilitiesWithSource(
|
|
129
|
-
modelId: string,
|
|
130
|
-
provider: string,
|
|
131
|
-
userMap: UserModelCapabilitiesMap | null,
|
|
132
|
-
envCaps: ModelCapabilities | null,
|
|
133
|
-
): ResolvedModelCapabilities {
|
|
134
|
-
if (envCaps) {
|
|
135
|
-
return { capabilities: envCaps, source: "env" };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const exact = userMap?.get(modelId.trim());
|
|
139
|
-
if (exact) {
|
|
140
|
-
return { capabilities: exact, source: "yaml" };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const builtin = matchBuiltinCapabilities(provider, modelId);
|
|
144
|
-
if (builtin) {
|
|
145
|
-
return { capabilities: builtin, source: "builtin" };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return { capabilities: { ...DEFAULT_CAPABILITIES }, source: "default" };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export function resolveModelCapabilities(
|
|
152
|
-
modelId: string,
|
|
153
|
-
provider: string,
|
|
154
|
-
userMap: UserModelCapabilitiesMap | null,
|
|
155
|
-
envCaps: ModelCapabilities | null,
|
|
156
|
-
): ModelCapabilities {
|
|
157
|
-
return resolveModelCapabilitiesWithSource(modelId, provider, userMap, envCaps)
|
|
158
|
-
.capabilities;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** Capabilities for each leg in the model chain (same length as `chain`). */
|
|
162
|
-
export function resolveModelChainCapabilities(
|
|
163
|
-
chain: ModelLeg[],
|
|
164
|
-
dataDirAbsolute: string,
|
|
165
|
-
envCaps: ModelCapabilities | null,
|
|
166
|
-
): {
|
|
167
|
-
chainCaps: ModelCapabilities[];
|
|
168
|
-
userMap: UserModelCapabilitiesMap | null;
|
|
169
|
-
} {
|
|
170
|
-
const userMap = loadUserModelCapabilitiesMap(dataDirAbsolute);
|
|
171
|
-
const chainCaps = chain.map((leg) =>
|
|
172
|
-
resolveModelCapabilities(leg.model, leg.provider, userMap, envCaps),
|
|
173
|
-
);
|
|
174
|
-
return { chainCaps, userMap };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export function chainSupportsRequirements(
|
|
178
|
-
requires: ModelCapabilityKey[],
|
|
179
|
-
chainCaps: ModelCapabilities[],
|
|
180
|
-
): boolean {
|
|
181
|
-
if (requires.length === 0) return true;
|
|
182
|
-
return chainCaps.some((caps) => requires.every((key) => caps[key] === true));
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/** Log warnings for models that fell back to defaults (once per distinct model id). */
|
|
186
|
-
export function logUnknownModelCapabilityWarnings(
|
|
187
|
-
chain: ModelLeg[],
|
|
188
|
-
dataDirAbsolute: string,
|
|
189
|
-
envCaps: ModelCapabilities | null,
|
|
190
|
-
log: { warn: (msg: string, obj?: Record<string, unknown>) => void },
|
|
191
|
-
): void {
|
|
192
|
-
if (envCaps) return;
|
|
193
|
-
const userMap = loadUserModelCapabilitiesMap(dataDirAbsolute);
|
|
194
|
-
const seen = new Set<string>();
|
|
195
|
-
|
|
196
|
-
for (const leg of chain) {
|
|
197
|
-
const id = leg.model.trim();
|
|
198
|
-
if (seen.has(id)) continue;
|
|
199
|
-
seen.add(id);
|
|
200
|
-
|
|
201
|
-
const { source } = resolveModelCapabilitiesWithSource(
|
|
202
|
-
id,
|
|
203
|
-
leg.provider,
|
|
204
|
-
userMap,
|
|
205
|
-
null,
|
|
206
|
-
);
|
|
207
|
-
if (source === "default") {
|
|
208
|
-
log.warn(
|
|
209
|
-
`Model "${id}" not in built-in capability map and not in model-capabilities.yaml; assuming default capabilities (tools=true, vision=false). Set MERCURY_MODEL_CAPABILITIES or add .mercury/model-capabilities.yaml to override.`,
|
|
210
|
-
{ model: id },
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export function logExtensionCapabilityMismatches(
|
|
217
|
-
extensions: Array<{ name: string; requires?: ModelCapabilityKey[] }>,
|
|
218
|
-
chainCaps: ModelCapabilities[],
|
|
219
|
-
log: { warn: (msg: string, obj?: Record<string, unknown>) => void },
|
|
220
|
-
): void {
|
|
221
|
-
for (const ext of extensions) {
|
|
222
|
-
const req = ext.requires;
|
|
223
|
-
if (!req?.length) continue;
|
|
224
|
-
if (!chainSupportsRequirements(req, chainCaps)) {
|
|
225
|
-
log.warn(
|
|
226
|
-
`Extension "${ext.name}" requires ${req.join(", ")} but no model leg in MERCURY_MODEL_CHAIN supports it; extension skills will not be installed.`,
|
|
227
|
-
{ extension: ext.name, requires: req },
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Model capability flags — used to adapt prompts, pi tool flags, and skill installation.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { getModels, type KnownProvider } from "@earendil-works/pi-ai";
|
|
8
|
+
import { parse as parseYaml } from "yaml";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import type { ModelLeg } from "../config.js";
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_CAPABILITIES,
|
|
13
|
+
type ModelCapabilities,
|
|
14
|
+
type ModelCapabilityKey,
|
|
15
|
+
} from "./model-capabilities-core.js";
|
|
16
|
+
|
|
17
|
+
export type {
|
|
18
|
+
ModelCapabilities,
|
|
19
|
+
ModelCapabilityKey,
|
|
20
|
+
} from "./model-capabilities-core.js";
|
|
21
|
+
export { DEFAULT_CAPABILITIES } from "./model-capabilities-core.js";
|
|
22
|
+
|
|
23
|
+
export type CapabilityResolveSource = "env" | "yaml" | "builtin" | "default";
|
|
24
|
+
|
|
25
|
+
export type ResolvedModelCapabilities = {
|
|
26
|
+
capabilities: ModelCapabilities;
|
|
27
|
+
source: CapabilityResolveSource;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function mergePartialCapabilities(
|
|
31
|
+
partial: Partial<Record<ModelCapabilityKey, unknown>>,
|
|
32
|
+
): ModelCapabilities {
|
|
33
|
+
const out = { ...DEFAULT_CAPABILITIES };
|
|
34
|
+
for (const k of Object.keys(partial) as ModelCapabilityKey[]) {
|
|
35
|
+
const v = partial[k];
|
|
36
|
+
if (typeof v === "boolean") out[k] = v;
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const partialCapsSchema = z.object({
|
|
42
|
+
tools: z.boolean().optional(),
|
|
43
|
+
vision: z.boolean().optional(),
|
|
44
|
+
audio_input: z.boolean().optional(),
|
|
45
|
+
audio_output: z.boolean().optional(),
|
|
46
|
+
extended_thinking: z.boolean().optional(),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const yamlFileSchema = z.object({
|
|
50
|
+
models: z.record(z.string(), partialCapsSchema),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export type UserModelCapabilitiesMap = Map<string, ModelCapabilities>;
|
|
54
|
+
|
|
55
|
+
/** Path to optional user overrides: `<dataDir>/model-capabilities.yaml` */
|
|
56
|
+
export function modelCapabilitiesYamlPath(dataDirAbsolute: string): string {
|
|
57
|
+
return path.join(dataDirAbsolute, "model-capabilities.yaml");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Load `.mercury/model-capabilities.yaml` (under resolved data dir).
|
|
62
|
+
* Returns null if missing or invalid (callers may log).
|
|
63
|
+
*/
|
|
64
|
+
export function loadUserModelCapabilitiesMap(
|
|
65
|
+
dataDirAbsolute: string,
|
|
66
|
+
): UserModelCapabilitiesMap | null {
|
|
67
|
+
const yamlPath = modelCapabilitiesYamlPath(dataDirAbsolute);
|
|
68
|
+
if (!existsSync(yamlPath)) return null;
|
|
69
|
+
try {
|
|
70
|
+
const raw = readFileSync(yamlPath, "utf8");
|
|
71
|
+
const doc = parseYaml(raw) as unknown;
|
|
72
|
+
const parsed = yamlFileSchema.safeParse(doc);
|
|
73
|
+
if (!parsed.success) return null;
|
|
74
|
+
const map = new Map<string, ModelCapabilities>();
|
|
75
|
+
for (const [modelId, partial] of Object.entries(parsed.data.models)) {
|
|
76
|
+
map.set(modelId.trim(), mergePartialCapabilities(partial));
|
|
77
|
+
}
|
|
78
|
+
return map;
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Look up capabilities for a model using pi's MODELS registry as the source of truth.
|
|
86
|
+
* Derives: vision (image in input), audio_input (audio in input), extended_thinking (reasoning).
|
|
87
|
+
* tools defaults to true — pi has no tools field; all pi-known models support tool use.
|
|
88
|
+
* audio_output is not tracked by pi; always false.
|
|
89
|
+
*/
|
|
90
|
+
function matchBuiltinCapabilities(
|
|
91
|
+
provider: string,
|
|
92
|
+
modelId: string,
|
|
93
|
+
): ModelCapabilities | null {
|
|
94
|
+
// Cast to KnownProvider — getModels returns [] for unrecognised providers at runtime
|
|
95
|
+
const model = getModels(provider as KnownProvider).find(
|
|
96
|
+
(m) => m.id === modelId,
|
|
97
|
+
);
|
|
98
|
+
if (!model) return null;
|
|
99
|
+
return {
|
|
100
|
+
tools: true,
|
|
101
|
+
vision: model.input.includes("image"),
|
|
102
|
+
audio_input: false, // pi types input as ("text"|"image")[]; no audio models exist yet
|
|
103
|
+
audio_output: false,
|
|
104
|
+
extended_thinking: model.reasoning,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Parse MERCURY_MODEL_CAPABILITIES JSON; returns null if unset or invalid. */
|
|
109
|
+
export function parseModelCapabilitiesEnv(
|
|
110
|
+
raw: string | undefined,
|
|
111
|
+
): ModelCapabilities | null {
|
|
112
|
+
const trimmed = raw?.trim();
|
|
113
|
+
if (!trimmed) return null;
|
|
114
|
+
try {
|
|
115
|
+
const json = JSON.parse(trimmed) as unknown;
|
|
116
|
+
const r = partialCapsSchema.safeParse(json);
|
|
117
|
+
if (!r.success) return null;
|
|
118
|
+
return mergePartialCapabilities(r.data);
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Resolve capabilities for one model id (single leg).
|
|
126
|
+
* Priority: env global override → YAML exact model key → pi MODELS lookup → default.
|
|
127
|
+
*/
|
|
128
|
+
export function resolveModelCapabilitiesWithSource(
|
|
129
|
+
modelId: string,
|
|
130
|
+
provider: string,
|
|
131
|
+
userMap: UserModelCapabilitiesMap | null,
|
|
132
|
+
envCaps: ModelCapabilities | null,
|
|
133
|
+
): ResolvedModelCapabilities {
|
|
134
|
+
if (envCaps) {
|
|
135
|
+
return { capabilities: envCaps, source: "env" };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const exact = userMap?.get(modelId.trim());
|
|
139
|
+
if (exact) {
|
|
140
|
+
return { capabilities: exact, source: "yaml" };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const builtin = matchBuiltinCapabilities(provider, modelId);
|
|
144
|
+
if (builtin) {
|
|
145
|
+
return { capabilities: builtin, source: "builtin" };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { capabilities: { ...DEFAULT_CAPABILITIES }, source: "default" };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function resolveModelCapabilities(
|
|
152
|
+
modelId: string,
|
|
153
|
+
provider: string,
|
|
154
|
+
userMap: UserModelCapabilitiesMap | null,
|
|
155
|
+
envCaps: ModelCapabilities | null,
|
|
156
|
+
): ModelCapabilities {
|
|
157
|
+
return resolveModelCapabilitiesWithSource(modelId, provider, userMap, envCaps)
|
|
158
|
+
.capabilities;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Capabilities for each leg in the model chain (same length as `chain`). */
|
|
162
|
+
export function resolveModelChainCapabilities(
|
|
163
|
+
chain: ModelLeg[],
|
|
164
|
+
dataDirAbsolute: string,
|
|
165
|
+
envCaps: ModelCapabilities | null,
|
|
166
|
+
): {
|
|
167
|
+
chainCaps: ModelCapabilities[];
|
|
168
|
+
userMap: UserModelCapabilitiesMap | null;
|
|
169
|
+
} {
|
|
170
|
+
const userMap = loadUserModelCapabilitiesMap(dataDirAbsolute);
|
|
171
|
+
const chainCaps = chain.map((leg) =>
|
|
172
|
+
resolveModelCapabilities(leg.model, leg.provider, userMap, envCaps),
|
|
173
|
+
);
|
|
174
|
+
return { chainCaps, userMap };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function chainSupportsRequirements(
|
|
178
|
+
requires: ModelCapabilityKey[],
|
|
179
|
+
chainCaps: ModelCapabilities[],
|
|
180
|
+
): boolean {
|
|
181
|
+
if (requires.length === 0) return true;
|
|
182
|
+
return chainCaps.some((caps) => requires.every((key) => caps[key] === true));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Log warnings for models that fell back to defaults (once per distinct model id). */
|
|
186
|
+
export function logUnknownModelCapabilityWarnings(
|
|
187
|
+
chain: ModelLeg[],
|
|
188
|
+
dataDirAbsolute: string,
|
|
189
|
+
envCaps: ModelCapabilities | null,
|
|
190
|
+
log: { warn: (msg: string, obj?: Record<string, unknown>) => void },
|
|
191
|
+
): void {
|
|
192
|
+
if (envCaps) return;
|
|
193
|
+
const userMap = loadUserModelCapabilitiesMap(dataDirAbsolute);
|
|
194
|
+
const seen = new Set<string>();
|
|
195
|
+
|
|
196
|
+
for (const leg of chain) {
|
|
197
|
+
const id = leg.model.trim();
|
|
198
|
+
if (seen.has(id)) continue;
|
|
199
|
+
seen.add(id);
|
|
200
|
+
|
|
201
|
+
const { source } = resolveModelCapabilitiesWithSource(
|
|
202
|
+
id,
|
|
203
|
+
leg.provider,
|
|
204
|
+
userMap,
|
|
205
|
+
null,
|
|
206
|
+
);
|
|
207
|
+
if (source === "default") {
|
|
208
|
+
log.warn(
|
|
209
|
+
`Model "${id}" not in built-in capability map and not in model-capabilities.yaml; assuming default capabilities (tools=true, vision=false). Set MERCURY_MODEL_CAPABILITIES or add .mercury/model-capabilities.yaml to override.`,
|
|
210
|
+
{ model: id },
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function logExtensionCapabilityMismatches(
|
|
217
|
+
extensions: Array<{ name: string; requires?: ModelCapabilityKey[] }>,
|
|
218
|
+
chainCaps: ModelCapabilities[],
|
|
219
|
+
log: { warn: (msg: string, obj?: Record<string, unknown>) => void },
|
|
220
|
+
): void {
|
|
221
|
+
for (const ext of extensions) {
|
|
222
|
+
const req = ext.requires;
|
|
223
|
+
if (!req?.length) continue;
|
|
224
|
+
if (!chainSupportsRequirements(req, chainCaps)) {
|
|
225
|
+
log.warn(
|
|
226
|
+
`Extension "${ext.name}" requires ${req.join(", ")} but no model leg in MERCURY_MODEL_CHAIN supports it; extension skills will not be installed.`,
|
|
227
|
+
{ extension: ext.name, requires: req },
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|