thoth-agents 0.12.0 → 0.13.1
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 +11 -2
- package/dist/{chunk-7F33WUSD.js → chunk-422L76R5.js} +2 -2
- package/dist/chunk-6O4GKDS7.js +207 -0
- package/dist/{chunk-SEF6PFM6.js → chunk-A7IGKE4R.js} +132 -157
- package/dist/{chunk-NY2IPJ7V.js → chunk-HFBCW4MD.js} +554 -236
- package/dist/{chunk-QOA2AETX.js → chunk-KUA3F4D6.js} +3 -3
- package/dist/cli/index.js +6 -6
- package/dist/cli/opencode-profiles.d.ts +10 -0
- package/dist/cli/tui/index.js +3 -3
- package/dist/config/loader.d.ts +1 -4
- package/dist/config/schema.d.ts +0 -22
- package/dist/index.js +824 -30
- package/dist/opencode/context.d.ts +16 -0
- package/dist/opencode/profile-rpc-handlers.d.ts +3 -0
- package/dist/opencode/profile-rpc.d.ts +688 -0
- package/dist/opencode/profile-runtime.d.ts +3 -0
- package/dist/opencode/profile-sessions.d.ts +18 -0
- package/dist/opencode/profiles/editor.d.ts +44 -0
- package/dist/opencode/profiles/index.d.ts +18 -0
- package/dist/opencode/profiles/resolver.d.ts +12 -0
- package/dist/opencode/profiles/store.d.ts +63 -0
- package/dist/opencode/profiles/types.d.ts +26 -0
- package/dist/opencode/tool-overrides/evaluator.d.ts +13 -0
- package/dist/opencode/tool-overrides/index.d.ts +5 -0
- package/dist/opencode/tool-overrides/store.d.ts +38 -0
- package/dist/opencode/tool-overrides/types.d.ts +32 -0
- package/dist/opencode/tool-overrides/validation.d.ts +5 -0
- package/dist/opencode/tool-overrides/wildcard.d.ts +3 -0
- package/dist/opencode/tool-rpc-handlers.d.ts +3 -0
- package/dist/opencode/tool-rpc.d.ts +428 -0
- package/dist/pi.js +2 -2
- package/dist/tui/fixtures.d.ts +2 -0
- package/dist/tui/flow.d.ts +16 -0
- package/dist/tui/index.d.ts +7 -0
- package/dist/tui/index.js +1386 -0
- package/dist/tui/model.d.ts +84 -0
- package/dist/tui/tools/fixtures.d.ts +3 -0
- package/dist/tui/tools/flow.d.ts +11 -0
- package/dist/tui/tools/model.d.ts +97 -0
- package/package.json +5 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/thoth-agents.schema.json +0 -70
|
@@ -19,6 +19,108 @@ function findPackageRoot(startDir) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// src/config/schema.ts
|
|
23
|
+
import { z } from "zod";
|
|
24
|
+
var AGENT_NAMES = [
|
|
25
|
+
"orchestrator",
|
|
26
|
+
"oracle",
|
|
27
|
+
"designer",
|
|
28
|
+
"explorer",
|
|
29
|
+
"librarian",
|
|
30
|
+
"worker"
|
|
31
|
+
];
|
|
32
|
+
var FALLBACK_AGENT_NAMES = [...AGENT_NAMES];
|
|
33
|
+
var MANUAL_AGENT_NAMES = [...AGENT_NAMES];
|
|
34
|
+
var ProviderModelIdSchema = z.string().regex(
|
|
35
|
+
/^[^/\s]+\/[^\s]+$/,
|
|
36
|
+
"Expected provider/model format (provider/.../model)"
|
|
37
|
+
);
|
|
38
|
+
var ManualAgentPlanSchema = z.object({
|
|
39
|
+
primary: ProviderModelIdSchema,
|
|
40
|
+
fallback1: ProviderModelIdSchema,
|
|
41
|
+
fallback2: ProviderModelIdSchema,
|
|
42
|
+
fallback3: ProviderModelIdSchema
|
|
43
|
+
}).superRefine((value, ctx) => {
|
|
44
|
+
const unique = /* @__PURE__ */ new Set([
|
|
45
|
+
value.primary,
|
|
46
|
+
value.fallback1,
|
|
47
|
+
value.fallback2,
|
|
48
|
+
value.fallback3
|
|
49
|
+
]);
|
|
50
|
+
if (unique.size !== 4) {
|
|
51
|
+
ctx.addIssue({
|
|
52
|
+
code: z.ZodIssueCode.custom,
|
|
53
|
+
message: "primary and fallbacks must be unique per agent"
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
var ManualPlanSchema = z.object({
|
|
58
|
+
orchestrator: ManualAgentPlanSchema,
|
|
59
|
+
oracle: ManualAgentPlanSchema,
|
|
60
|
+
designer: ManualAgentPlanSchema,
|
|
61
|
+
explorer: ManualAgentPlanSchema,
|
|
62
|
+
librarian: ManualAgentPlanSchema,
|
|
63
|
+
worker: ManualAgentPlanSchema
|
|
64
|
+
}).strict();
|
|
65
|
+
var AgentModelChainSchema = z.array(z.string()).min(1);
|
|
66
|
+
var SupportedAgentRecordKeySchema = z.string().regex(/^(?!quick$|deep$).+$/, "Unsupported removed agent role");
|
|
67
|
+
var FallbackChainsSchema = z.record(
|
|
68
|
+
SupportedAgentRecordKeySchema,
|
|
69
|
+
AgentModelChainSchema
|
|
70
|
+
);
|
|
71
|
+
var AgentOverrideConfigSchema = z.object({
|
|
72
|
+
model: z.union([
|
|
73
|
+
z.string(),
|
|
74
|
+
z.array(
|
|
75
|
+
z.union([
|
|
76
|
+
z.string(),
|
|
77
|
+
z.object({
|
|
78
|
+
id: z.string(),
|
|
79
|
+
variant: z.string().optional()
|
|
80
|
+
})
|
|
81
|
+
])
|
|
82
|
+
)
|
|
83
|
+
]).optional(),
|
|
84
|
+
/** Pi specialist catalog filter; omitted discovers all, [] discovers none. */
|
|
85
|
+
skills: z.array(z.string().trim().min(1)).optional(),
|
|
86
|
+
temperature: z.number().min(0).max(2).optional(),
|
|
87
|
+
steps: z.number().int().min(1).optional(),
|
|
88
|
+
variant: z.string().optional().catch(void 0)
|
|
89
|
+
});
|
|
90
|
+
var AgentNameSchema = z.enum(AGENT_NAMES);
|
|
91
|
+
var McpNameSchema = z.enum(["exa", "context7", "grep_app"]);
|
|
92
|
+
var CodexGenerationConfigSchema = z.object({
|
|
93
|
+
enabled: z.boolean().default(false),
|
|
94
|
+
outputRoot: z.string().optional(),
|
|
95
|
+
dryRun: z.boolean().default(true)
|
|
96
|
+
});
|
|
97
|
+
var ClaudeCodeGenerationConfigSchema = z.object({
|
|
98
|
+
enabled: z.boolean().default(false),
|
|
99
|
+
outputRoot: z.string().optional(),
|
|
100
|
+
dryRun: z.boolean().default(true)
|
|
101
|
+
});
|
|
102
|
+
var FailoverConfigSchema = z.object({
|
|
103
|
+
enabled: z.boolean().default(true),
|
|
104
|
+
timeoutMs: z.number().min(0).default(15e3),
|
|
105
|
+
retryDelayMs: z.number().min(0).default(500),
|
|
106
|
+
chains: FallbackChainsSchema.default({})
|
|
107
|
+
});
|
|
108
|
+
var PluginConfigSchema = z.object({
|
|
109
|
+
$schema: z.string().optional(),
|
|
110
|
+
setDefaultAgent: z.boolean().optional(),
|
|
111
|
+
scoringEngineVersion: z.enum(["v1", "v2-shadow", "v2"]).optional(),
|
|
112
|
+
balanceProviderUsage: z.boolean().optional(),
|
|
113
|
+
manualPlan: ManualPlanSchema.optional(),
|
|
114
|
+
agents: z.record(SupportedAgentRecordKeySchema, AgentOverrideConfigSchema).optional(),
|
|
115
|
+
disabled_mcps: z.array(z.string()).optional(),
|
|
116
|
+
fallback: FailoverConfigSchema.optional(),
|
|
117
|
+
codex: CodexGenerationConfigSchema.optional(),
|
|
118
|
+
claudeCode: ClaudeCodeGenerationConfigSchema.optional()
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// src/opencode/profiles/types.ts
|
|
122
|
+
var PROFILE_ROLES = AgentNameSchema.options;
|
|
123
|
+
|
|
22
124
|
// src/config/constants.ts
|
|
23
125
|
var SUBAGENT_NAMES = [
|
|
24
126
|
"explorer",
|
|
@@ -88,6 +190,302 @@ function getDefaultOpenCodeVariant(name) {
|
|
|
88
190
|
var DEFAULT_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
89
191
|
var MAX_POLL_TIME_MS = 5 * 60 * 1e3;
|
|
90
192
|
|
|
193
|
+
// src/opencode/profiles/resolver.ts
|
|
194
|
+
function configuredModelChain(config) {
|
|
195
|
+
const models = config?.model;
|
|
196
|
+
return (typeof models === "string" ? [models] : models ?? []).map(
|
|
197
|
+
(entry) => {
|
|
198
|
+
const value = typeof entry === "string" ? { id: entry } : { ...entry };
|
|
199
|
+
const variant = Array.isArray(models) ? value.variant : config?.variant;
|
|
200
|
+
return { ...value, ...variant ? { variant } : {} };
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
function resolveModelPair(config, inherited = {}) {
|
|
205
|
+
const first = configuredModelChain(config)[0];
|
|
206
|
+
const model = first?.id ?? inherited.model;
|
|
207
|
+
const variant = config?.model !== void 0 ? first?.variant : config?.variant ?? inherited.variant;
|
|
208
|
+
return { ...model ? { model } : {}, ...variant ? { variant } : {} };
|
|
209
|
+
}
|
|
210
|
+
function resolveRoleProfile(role, profile, roster) {
|
|
211
|
+
return resolveModelPair(profile.agents[role], resolveModelPair(roster[role]));
|
|
212
|
+
}
|
|
213
|
+
function defaultModelProfile(roster) {
|
|
214
|
+
return {
|
|
215
|
+
agents: Object.fromEntries(
|
|
216
|
+
PROFILE_ROLES.map((role) => [
|
|
217
|
+
role,
|
|
218
|
+
resolveRoleProfile(role, { agents: {} }, roster)
|
|
219
|
+
])
|
|
220
|
+
)
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function mergeProfileModelChain(profile, configured) {
|
|
224
|
+
const entries = profile.model ? [
|
|
225
|
+
{
|
|
226
|
+
id: profile.model,
|
|
227
|
+
...profile.variant ? { variant: profile.variant } : {}
|
|
228
|
+
},
|
|
229
|
+
...configured
|
|
230
|
+
] : configured;
|
|
231
|
+
const seen = /* @__PURE__ */ new Set();
|
|
232
|
+
return entries.filter(({ id }) => {
|
|
233
|
+
if (seen.has(id)) return false;
|
|
234
|
+
seen.add(id);
|
|
235
|
+
return true;
|
|
236
|
+
}).map((entry) => ({ ...entry }));
|
|
237
|
+
}
|
|
238
|
+
function formatModelString(profile) {
|
|
239
|
+
return profile.model ? `${profile.model}${profile.variant ? `#${profile.variant}` : ""}` : void 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/opencode/profiles/store.ts
|
|
243
|
+
import { randomUUID } from "crypto";
|
|
244
|
+
import fs from "fs";
|
|
245
|
+
import os from "os";
|
|
246
|
+
import path from "path";
|
|
247
|
+
import { isDeepStrictEqual } from "util";
|
|
248
|
+
var DEFAULT_PROFILE = "default";
|
|
249
|
+
function modelProfilePaths(options) {
|
|
250
|
+
if (options.paths) return { ...options.paths };
|
|
251
|
+
const env = options.env ?? process.env;
|
|
252
|
+
const configDir = options.configDir ?? (env.OPENCODE_CONFIG_DIR?.trim() || path.join(
|
|
253
|
+
env.XDG_CONFIG_HOME || path.join(options.homeDir ?? os.homedir(), ".config"),
|
|
254
|
+
"opencode"
|
|
255
|
+
));
|
|
256
|
+
return {
|
|
257
|
+
global: path.join(configDir, "thoth", "profiles.json"),
|
|
258
|
+
project: path.join(options.cwd, ".opencode", "thoth", "profiles.json")
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function object(value) {
|
|
262
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
263
|
+
}
|
|
264
|
+
function validateProfileName(value, writable = false) {
|
|
265
|
+
if (typeof value !== "string" || !value.trim())
|
|
266
|
+
throw new Error("Profile name must be non-empty");
|
|
267
|
+
const name = value.trim();
|
|
268
|
+
if (writable && name === DEFAULT_PROFILE)
|
|
269
|
+
throw new Error("default is a reserved read-only virtual profile");
|
|
270
|
+
return name;
|
|
271
|
+
}
|
|
272
|
+
function parseRoleProfile(value) {
|
|
273
|
+
if (!object(value)) throw new Error("Role override must be an object");
|
|
274
|
+
if (value.model !== void 0 && (typeof value.model !== "string" || !/^[^/\s#]+\/[^\s#]+$/.test(value.model)))
|
|
275
|
+
throw new Error("Invalid model; expected provider/id without #variant");
|
|
276
|
+
if (value.variant !== void 0 && (typeof value.variant !== "string" || !/^[^\s#]+$/.test(value.variant)))
|
|
277
|
+
throw new Error(
|
|
278
|
+
"Invalid variant; expected a non-empty string without whitespace or #"
|
|
279
|
+
);
|
|
280
|
+
return {
|
|
281
|
+
...value.model !== void 0 ? { model: value.model } : {},
|
|
282
|
+
...value.variant !== void 0 ? { variant: value.variant } : {}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function parseNamedModelProfile(value) {
|
|
286
|
+
if (!object(value) || !object(value.agents))
|
|
287
|
+
throw new Error("Profile must contain an agents object");
|
|
288
|
+
const agents = {};
|
|
289
|
+
for (const [role, override] of Object.entries(value.agents)) {
|
|
290
|
+
const canonical = PROFILE_ROLES.find((name) => name === role);
|
|
291
|
+
if (!canonical) throw new Error(`Unknown profile role: ${role}`);
|
|
292
|
+
agents[canonical] = parseRoleProfile(override);
|
|
293
|
+
}
|
|
294
|
+
return { agents };
|
|
295
|
+
}
|
|
296
|
+
function parseModelProfilesDocument(raw) {
|
|
297
|
+
if (!object(raw) || raw.version !== 1 || !object(raw.profiles))
|
|
298
|
+
throw new Error("Expected version 1 and a profiles object");
|
|
299
|
+
const profiles = /* @__PURE__ */ Object.create(null);
|
|
300
|
+
for (const [key, value] of Object.entries(raw.profiles)) {
|
|
301
|
+
const name = validateProfileName(key, true);
|
|
302
|
+
if (Object.hasOwn(profiles, name))
|
|
303
|
+
throw new Error(`Duplicate profile: ${name}`);
|
|
304
|
+
profiles[name] = parseNamedModelProfile(value);
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
version: 1,
|
|
308
|
+
...raw.active !== void 0 ? { active: validateProfileName(raw.active) } : {},
|
|
309
|
+
profiles
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
var emptyDocument = () => ({
|
|
313
|
+
version: 1,
|
|
314
|
+
profiles: /* @__PURE__ */ Object.create(null)
|
|
315
|
+
});
|
|
316
|
+
var ProfileConflictError = class extends Error {
|
|
317
|
+
constructor(scope, profileName) {
|
|
318
|
+
super(
|
|
319
|
+
`Profile changed on disk: ${scope}:${profileName}; reload before saving.`
|
|
320
|
+
);
|
|
321
|
+
this.scope = scope;
|
|
322
|
+
this.profileName = profileName;
|
|
323
|
+
this.name = "ProfileConflictError";
|
|
324
|
+
}
|
|
325
|
+
scope;
|
|
326
|
+
profileName;
|
|
327
|
+
code = "PROFILE_CONFLICT";
|
|
328
|
+
};
|
|
329
|
+
var ModelProfileStore = class {
|
|
330
|
+
constructor(options) {
|
|
331
|
+
this.options = options;
|
|
332
|
+
this.paths = modelProfilePaths(options);
|
|
333
|
+
}
|
|
334
|
+
options;
|
|
335
|
+
paths;
|
|
336
|
+
defaultProfile() {
|
|
337
|
+
return defaultModelProfile(this.options.roster ?? {});
|
|
338
|
+
}
|
|
339
|
+
load(scope) {
|
|
340
|
+
try {
|
|
341
|
+
return parseModelProfilesDocument(
|
|
342
|
+
JSON.parse(fs.readFileSync(this.paths[scope], "utf8"))
|
|
343
|
+
);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
if (error.code === "ENOENT")
|
|
346
|
+
return emptyDocument();
|
|
347
|
+
throw new Error(
|
|
348
|
+
`Cannot read model profiles ${this.paths[scope]}: ${String(error)}`,
|
|
349
|
+
{ cause: error }
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
read(scope, warnings = []) {
|
|
354
|
+
try {
|
|
355
|
+
return this.load(scope);
|
|
356
|
+
} catch (error) {
|
|
357
|
+
const warning = String(error);
|
|
358
|
+
warnings.push(warning);
|
|
359
|
+
this.options.warn?.(warning);
|
|
360
|
+
return emptyDocument();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
lookup(name, scope) {
|
|
364
|
+
const key = validateProfileName(name);
|
|
365
|
+
if (key === DEFAULT_PROFILE) return { profile: this.defaultProfile() };
|
|
366
|
+
for (const source of scope ? [scope] : ["project", "global"]) {
|
|
367
|
+
const profiles = this.read(source).profiles;
|
|
368
|
+
if (Object.hasOwn(profiles, key))
|
|
369
|
+
return { profile: profiles[key], scope: source };
|
|
370
|
+
}
|
|
371
|
+
return void 0;
|
|
372
|
+
}
|
|
373
|
+
write(scope, document) {
|
|
374
|
+
const validated = parseModelProfilesDocument(document);
|
|
375
|
+
const file = this.paths[scope];
|
|
376
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
377
|
+
const temp = `${file}.${randomUUID()}.tmp`;
|
|
378
|
+
try {
|
|
379
|
+
fs.writeFileSync(temp, `${JSON.stringify(validated, null, 2)}
|
|
380
|
+
`, {
|
|
381
|
+
encoding: "utf8",
|
|
382
|
+
flag: "wx"
|
|
383
|
+
});
|
|
384
|
+
fs.renameSync(temp, file);
|
|
385
|
+
} finally {
|
|
386
|
+
if (fs.existsSync(temp)) fs.unlinkSync(temp);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/** Re-read before writing. An expected baseline enables same-profile conflict detection. */
|
|
390
|
+
save(scope, name, value, options = {}) {
|
|
391
|
+
const key = validateProfileName(name, true);
|
|
392
|
+
const next = parseNamedModelProfile(value);
|
|
393
|
+
const document = this.load(scope);
|
|
394
|
+
const existing = Object.hasOwn(document.profiles, key) ? document.profiles[key] : null;
|
|
395
|
+
if (Object.hasOwn(options, "expected") && !isDeepStrictEqual(existing, options.expected))
|
|
396
|
+
throw new ProfileConflictError(scope, key);
|
|
397
|
+
document.profiles[key] = next;
|
|
398
|
+
this.write(scope, document);
|
|
399
|
+
}
|
|
400
|
+
create(scope, name, value) {
|
|
401
|
+
const key = validateProfileName(name, true);
|
|
402
|
+
if (Object.hasOwn(this.load(scope).profiles, key))
|
|
403
|
+
throw new Error(`Profile already exists: ${key}`);
|
|
404
|
+
this.save(scope, key, value, { expected: null });
|
|
405
|
+
}
|
|
406
|
+
/** Pins that actually resolved to a global profile follow rename/delete. */
|
|
407
|
+
projectPointsToGlobal(name) {
|
|
408
|
+
const project = this.load("project");
|
|
409
|
+
return project.active === name && !Object.hasOwn(project.profiles, name);
|
|
410
|
+
}
|
|
411
|
+
rename(scope, source, target) {
|
|
412
|
+
const from = validateProfileName(source, true);
|
|
413
|
+
const to = validateProfileName(target, true);
|
|
414
|
+
const document = this.load(scope);
|
|
415
|
+
if (!Object.hasOwn(document.profiles, from))
|
|
416
|
+
throw new Error(`Profile not found: ${from}`);
|
|
417
|
+
if (Object.hasOwn(document.profiles, to))
|
|
418
|
+
throw new Error(`Profile already exists: ${to}`);
|
|
419
|
+
const followPin = scope === "global" && this.projectPointsToGlobal(from);
|
|
420
|
+
document.profiles[to] = document.profiles[from];
|
|
421
|
+
delete document.profiles[from];
|
|
422
|
+
if (document.active === from) document.active = to;
|
|
423
|
+
this.write(scope, document);
|
|
424
|
+
if (followPin) this.setProjectPin(to);
|
|
425
|
+
}
|
|
426
|
+
delete(scope, name) {
|
|
427
|
+
const key = validateProfileName(name, true);
|
|
428
|
+
const document = this.load(scope);
|
|
429
|
+
if (!Object.hasOwn(document.profiles, key))
|
|
430
|
+
throw new Error(`Profile not found: ${key}`);
|
|
431
|
+
const clearPin = scope === "global" && this.projectPointsToGlobal(key);
|
|
432
|
+
delete document.profiles[key];
|
|
433
|
+
const clearedActive = document.active === key;
|
|
434
|
+
if (clearedActive) delete document.active;
|
|
435
|
+
this.write(scope, document);
|
|
436
|
+
if (clearPin) this.setProjectPin();
|
|
437
|
+
return { clearedActive };
|
|
438
|
+
}
|
|
439
|
+
setActive(scope, name) {
|
|
440
|
+
const document = this.load(scope);
|
|
441
|
+
if (name === void 0) delete document.active;
|
|
442
|
+
else {
|
|
443
|
+
const key = validateProfileName(name);
|
|
444
|
+
if (key !== DEFAULT_PROFILE && !(scope === "global" ? Object.hasOwn(document.profiles, key) : this.lookup(key)))
|
|
445
|
+
throw new Error(`Profile not found: ${key}`);
|
|
446
|
+
document.active = key;
|
|
447
|
+
}
|
|
448
|
+
this.write(scope, document);
|
|
449
|
+
}
|
|
450
|
+
setGlobalActive(name) {
|
|
451
|
+
this.setActive("global", name);
|
|
452
|
+
}
|
|
453
|
+
setProjectPin(name) {
|
|
454
|
+
this.setActive("project", name);
|
|
455
|
+
}
|
|
456
|
+
resolve(sessionSelection) {
|
|
457
|
+
const warnings = [];
|
|
458
|
+
const documents = {
|
|
459
|
+
global: this.read("global", warnings),
|
|
460
|
+
project: this.read("project", warnings)
|
|
461
|
+
};
|
|
462
|
+
for (const [level, name] of [
|
|
463
|
+
["session", sessionSelection],
|
|
464
|
+
["project", documents.project.active],
|
|
465
|
+
["global", documents.global.active]
|
|
466
|
+
]) {
|
|
467
|
+
if (!name) continue;
|
|
468
|
+
if (name === DEFAULT_PROFILE)
|
|
469
|
+
return { name, profile: this.defaultProfile(), warnings };
|
|
470
|
+
const scope = Object.hasOwn(documents.project.profiles, name) ? "project" : "global";
|
|
471
|
+
if (Object.hasOwn(documents[scope].profiles, name) && (level !== "global" || Object.hasOwn(documents.global.profiles, name)))
|
|
472
|
+
return {
|
|
473
|
+
name,
|
|
474
|
+
scope,
|
|
475
|
+
profile: documents[scope].profiles[name],
|
|
476
|
+
warnings
|
|
477
|
+
};
|
|
478
|
+
const warning = `Missing ${level} model profile "${name}"; falling back.`;
|
|
479
|
+
warnings.push(warning);
|
|
480
|
+
this.options.warn?.(warning);
|
|
481
|
+
}
|
|
482
|
+
return { name: DEFAULT_PROFILE, profile: this.defaultProfile(), warnings };
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
// src/opencode/profiles/editor.ts
|
|
487
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
488
|
+
|
|
91
489
|
// src/cli/package-version.ts
|
|
92
490
|
import { readFileSync } from "fs";
|
|
93
491
|
import { dirname as dirname2, join as join2 } from "path";
|
|
@@ -181,8 +579,8 @@ function resolveExecutingPackageVersion(options = {}) {
|
|
|
181
579
|
}
|
|
182
580
|
|
|
183
581
|
// src/config/loader.ts
|
|
184
|
-
import * as
|
|
185
|
-
import * as
|
|
582
|
+
import * as fs2 from "fs";
|
|
583
|
+
import * as path2 from "path";
|
|
186
584
|
|
|
187
585
|
// src/cli/config-io.ts
|
|
188
586
|
import {
|
|
@@ -259,111 +657,6 @@ function ensureOpenCodeConfigDir() {
|
|
|
259
657
|
}
|
|
260
658
|
}
|
|
261
659
|
|
|
262
|
-
// src/config/schema.ts
|
|
263
|
-
import { z } from "zod";
|
|
264
|
-
var AGENT_NAMES = [
|
|
265
|
-
"orchestrator",
|
|
266
|
-
"oracle",
|
|
267
|
-
"designer",
|
|
268
|
-
"explorer",
|
|
269
|
-
"librarian",
|
|
270
|
-
"worker"
|
|
271
|
-
];
|
|
272
|
-
var FALLBACK_AGENT_NAMES = [...AGENT_NAMES];
|
|
273
|
-
var MANUAL_AGENT_NAMES = [...AGENT_NAMES];
|
|
274
|
-
var ProviderModelIdSchema = z.string().regex(
|
|
275
|
-
/^[^/\s]+\/[^\s]+$/,
|
|
276
|
-
"Expected provider/model format (provider/.../model)"
|
|
277
|
-
);
|
|
278
|
-
var ManualAgentPlanSchema = z.object({
|
|
279
|
-
primary: ProviderModelIdSchema,
|
|
280
|
-
fallback1: ProviderModelIdSchema,
|
|
281
|
-
fallback2: ProviderModelIdSchema,
|
|
282
|
-
fallback3: ProviderModelIdSchema
|
|
283
|
-
}).superRefine((value, ctx) => {
|
|
284
|
-
const unique = /* @__PURE__ */ new Set([
|
|
285
|
-
value.primary,
|
|
286
|
-
value.fallback1,
|
|
287
|
-
value.fallback2,
|
|
288
|
-
value.fallback3
|
|
289
|
-
]);
|
|
290
|
-
if (unique.size !== 4) {
|
|
291
|
-
ctx.addIssue({
|
|
292
|
-
code: z.ZodIssueCode.custom,
|
|
293
|
-
message: "primary and fallbacks must be unique per agent"
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
var ManualPlanSchema = z.object({
|
|
298
|
-
orchestrator: ManualAgentPlanSchema,
|
|
299
|
-
oracle: ManualAgentPlanSchema,
|
|
300
|
-
designer: ManualAgentPlanSchema,
|
|
301
|
-
explorer: ManualAgentPlanSchema,
|
|
302
|
-
librarian: ManualAgentPlanSchema,
|
|
303
|
-
worker: ManualAgentPlanSchema
|
|
304
|
-
}).strict();
|
|
305
|
-
var AgentModelChainSchema = z.array(z.string()).min(1);
|
|
306
|
-
var SupportedAgentRecordKeySchema = z.string().regex(/^(?!quick$|deep$).+$/, "Unsupported removed agent role");
|
|
307
|
-
var FallbackChainsSchema = z.record(
|
|
308
|
-
SupportedAgentRecordKeySchema,
|
|
309
|
-
AgentModelChainSchema
|
|
310
|
-
);
|
|
311
|
-
var AgentOverrideConfigSchema = z.object({
|
|
312
|
-
model: z.union([
|
|
313
|
-
z.string(),
|
|
314
|
-
z.array(
|
|
315
|
-
z.union([
|
|
316
|
-
z.string(),
|
|
317
|
-
z.object({
|
|
318
|
-
id: z.string(),
|
|
319
|
-
variant: z.string().optional()
|
|
320
|
-
})
|
|
321
|
-
])
|
|
322
|
-
)
|
|
323
|
-
]).optional(),
|
|
324
|
-
/** Pi specialist catalog filter; omitted discovers all, [] discovers none. */
|
|
325
|
-
skills: z.array(z.string().trim().min(1)).optional(),
|
|
326
|
-
temperature: z.number().min(0).max(2).optional(),
|
|
327
|
-
steps: z.number().int().min(1).optional(),
|
|
328
|
-
variant: z.string().optional().catch(void 0)
|
|
329
|
-
});
|
|
330
|
-
var PresetSchema = z.record(
|
|
331
|
-
SupportedAgentRecordKeySchema,
|
|
332
|
-
AgentOverrideConfigSchema
|
|
333
|
-
);
|
|
334
|
-
var AgentNameSchema = z.enum(AGENT_NAMES);
|
|
335
|
-
var McpNameSchema = z.enum(["exa", "context7", "grep_app"]);
|
|
336
|
-
var CodexGenerationConfigSchema = z.object({
|
|
337
|
-
enabled: z.boolean().default(false),
|
|
338
|
-
outputRoot: z.string().optional(),
|
|
339
|
-
dryRun: z.boolean().default(true)
|
|
340
|
-
});
|
|
341
|
-
var ClaudeCodeGenerationConfigSchema = z.object({
|
|
342
|
-
enabled: z.boolean().default(false),
|
|
343
|
-
outputRoot: z.string().optional(),
|
|
344
|
-
dryRun: z.boolean().default(true)
|
|
345
|
-
});
|
|
346
|
-
var FailoverConfigSchema = z.object({
|
|
347
|
-
enabled: z.boolean().default(true),
|
|
348
|
-
timeoutMs: z.number().min(0).default(15e3),
|
|
349
|
-
retryDelayMs: z.number().min(0).default(500),
|
|
350
|
-
chains: FallbackChainsSchema.default({})
|
|
351
|
-
});
|
|
352
|
-
var PluginConfigSchema = z.object({
|
|
353
|
-
$schema: z.string().optional(),
|
|
354
|
-
preset: z.string().optional(),
|
|
355
|
-
setDefaultAgent: z.boolean().optional(),
|
|
356
|
-
scoringEngineVersion: z.enum(["v1", "v2-shadow", "v2"]).optional(),
|
|
357
|
-
balanceProviderUsage: z.boolean().optional(),
|
|
358
|
-
manualPlan: ManualPlanSchema.optional(),
|
|
359
|
-
presets: z.record(z.string(), PresetSchema).optional(),
|
|
360
|
-
agents: z.record(SupportedAgentRecordKeySchema, AgentOverrideConfigSchema).optional(),
|
|
361
|
-
disabled_mcps: z.array(z.string()).optional(),
|
|
362
|
-
fallback: FailoverConfigSchema.optional(),
|
|
363
|
-
codex: CodexGenerationConfigSchema.optional(),
|
|
364
|
-
claudeCode: ClaudeCodeGenerationConfigSchema.optional()
|
|
365
|
-
});
|
|
366
|
-
|
|
367
660
|
// src/config/utils.ts
|
|
368
661
|
function getAgentOverride(config, name) {
|
|
369
662
|
return config?.agents?.[name];
|
|
@@ -399,28 +692,74 @@ var MODEL_MAPPINGS = {
|
|
|
399
692
|
openai: buildOpenAIModelMappings()
|
|
400
693
|
};
|
|
401
694
|
function generateLiteConfig(_installConfig) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
695
|
+
return { $schema: THOTH_AGENTS_CONFIG_SCHEMA_URL };
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// src/cli/opencode-profiles.ts
|
|
699
|
+
function openCodeProfileStore(roster) {
|
|
700
|
+
return new ModelProfileStore({
|
|
701
|
+
cwd: process.cwd(),
|
|
702
|
+
configDir: getConfigDir(),
|
|
703
|
+
roster
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
function activeOpenCodeProfile() {
|
|
707
|
+
const store = openCodeProfileStore();
|
|
708
|
+
const document = store.read("global");
|
|
709
|
+
return {
|
|
710
|
+
name: document.active,
|
|
711
|
+
profile: document.active ? store.lookup(document.active, "global")?.profile : void 0
|
|
412
712
|
};
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
713
|
+
}
|
|
714
|
+
function effectiveOpenCodeProfile(config) {
|
|
715
|
+
const root = config?.agents ?? {};
|
|
716
|
+
const roster = Object.fromEntries(
|
|
717
|
+
PROFILE_ROLES.map((role) => [
|
|
718
|
+
role,
|
|
719
|
+
resolveModelPair(root[role], MODEL_MAPPINGS.openai[role])
|
|
720
|
+
])
|
|
721
|
+
);
|
|
722
|
+
const selected = activeOpenCodeProfile().profile ?? { agents: {} };
|
|
723
|
+
return {
|
|
724
|
+
agents: Object.fromEntries(
|
|
725
|
+
PROFILE_ROLES.map((role) => [
|
|
726
|
+
role,
|
|
727
|
+
resolveRoleProfile(role, selected, roster)
|
|
419
728
|
])
|
|
420
|
-
)
|
|
729
|
+
)
|
|
421
730
|
};
|
|
422
|
-
|
|
423
|
-
|
|
731
|
+
}
|
|
732
|
+
function syncOpenCodeProfile(reset = false) {
|
|
733
|
+
const store = openCodeProfileStore();
|
|
734
|
+
const document = store.read("global");
|
|
735
|
+
const names = ["openai"];
|
|
736
|
+
if (document.active === "agents" && document.profiles.agents)
|
|
737
|
+
names.push("agents");
|
|
738
|
+
for (const name of names) {
|
|
739
|
+
const previous = document.profiles[name]?.agents ?? {};
|
|
740
|
+
const agents = Object.fromEntries(
|
|
741
|
+
PROFILE_ROLES.map((role) => {
|
|
742
|
+
const current = previous[role];
|
|
743
|
+
const legacy = LEGACY_OPENAI_PRESET[role];
|
|
744
|
+
const advance = current?.model === `openai/${legacy.model}` && current?.variant === legacy.effort;
|
|
745
|
+
return [
|
|
746
|
+
role,
|
|
747
|
+
reset && name === "openai" || advance ? MODEL_MAPPINGS.openai[role] : resolveRoleProfile(
|
|
748
|
+
role,
|
|
749
|
+
{ agents: previous },
|
|
750
|
+
MODEL_MAPPINGS.openai
|
|
751
|
+
)
|
|
752
|
+
];
|
|
753
|
+
})
|
|
754
|
+
);
|
|
755
|
+
store.save("global", name, { agents });
|
|
756
|
+
}
|
|
757
|
+
if (!document.active || !store.lookup(document.active, "global"))
|
|
758
|
+
store.setGlobalActive("openai");
|
|
759
|
+
}
|
|
760
|
+
function withoutLegacyPresets(config) {
|
|
761
|
+
const { preset: _preset, presets: _presets, ...rest } = config;
|
|
762
|
+
return rest;
|
|
424
763
|
}
|
|
425
764
|
|
|
426
765
|
// src/cli/config-io.ts
|
|
@@ -436,23 +775,23 @@ function stripJsonComments(json) {
|
|
|
436
775
|
(match, comma, closing) => comma ? closing : match
|
|
437
776
|
);
|
|
438
777
|
}
|
|
439
|
-
function parseConfigFile(
|
|
778
|
+
function parseConfigFile(path3) {
|
|
440
779
|
try {
|
|
441
|
-
if (!existsSync3(
|
|
442
|
-
const stat = statSync(
|
|
780
|
+
if (!existsSync3(path3)) return { config: null };
|
|
781
|
+
const stat = statSync(path3);
|
|
443
782
|
if (stat.size === 0) return { config: null };
|
|
444
|
-
const content = readFileSync2(
|
|
783
|
+
const content = readFileSync2(path3, "utf-8");
|
|
445
784
|
if (content.trim().length === 0) return { config: null };
|
|
446
785
|
return { config: JSON.parse(stripJsonComments(content)) };
|
|
447
786
|
} catch (err) {
|
|
448
787
|
return { config: null, error: String(err) };
|
|
449
788
|
}
|
|
450
789
|
}
|
|
451
|
-
function parseConfig(
|
|
452
|
-
const result = parseConfigFile(
|
|
790
|
+
function parseConfig(path3) {
|
|
791
|
+
const result = parseConfigFile(path3);
|
|
453
792
|
if (result.config || result.error) return result;
|
|
454
|
-
if (
|
|
455
|
-
const jsoncPath =
|
|
793
|
+
if (path3.endsWith(".json")) {
|
|
794
|
+
const jsoncPath = path3.replace(/\.json$/, ".jsonc");
|
|
456
795
|
return parseConfigFile(jsoncPath);
|
|
457
796
|
}
|
|
458
797
|
return { config: null };
|
|
@@ -556,43 +895,6 @@ function configObject(value) {
|
|
|
556
895
|
throw new Error("Expected an object in managed lite configuration.");
|
|
557
896
|
return value;
|
|
558
897
|
}
|
|
559
|
-
function mergeLiteConfig(defaults, installed) {
|
|
560
|
-
const presets = { ...configObject(installed.presets) };
|
|
561
|
-
const shipped = configObject(configObject(defaults.presets).openai);
|
|
562
|
-
const activeName = typeof installed.preset === "string" ? installed.preset : "openai";
|
|
563
|
-
const rootAgents = { ...configObject(installed.agents) };
|
|
564
|
-
for (const name of /* @__PURE__ */ new Set(["openai", activeName])) {
|
|
565
|
-
const roles = { ...configObject(presets[name]) };
|
|
566
|
-
for (const [role, legacy] of Object.entries(LEGACY_OPENAI_PRESET)) {
|
|
567
|
-
const rootRole = name === activeName ? rootAgents[role] : void 0;
|
|
568
|
-
const current = {
|
|
569
|
-
...configObject(roles[role]),
|
|
570
|
-
...configObject(rootRole)
|
|
571
|
-
};
|
|
572
|
-
const advancesLegacy = current.model === `openai/${legacy.model}` && current.variant === legacy.effort;
|
|
573
|
-
if (roles[role] === void 0 && rootRole === void 0) {
|
|
574
|
-
roles[role] = shipped[role];
|
|
575
|
-
} else if (advancesLegacy) {
|
|
576
|
-
roles[role] = { ...current, ...configObject(shipped[role]) };
|
|
577
|
-
if (rootRole !== void 0)
|
|
578
|
-
rootAgents[role] = {
|
|
579
|
-
...configObject(rootRole),
|
|
580
|
-
...configObject(shipped[role])
|
|
581
|
-
};
|
|
582
|
-
} else {
|
|
583
|
-
roles[role] = current;
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
presets[name] = roles;
|
|
587
|
-
}
|
|
588
|
-
return {
|
|
589
|
-
...defaults,
|
|
590
|
-
...installed,
|
|
591
|
-
preset: activeName,
|
|
592
|
-
presets,
|
|
593
|
-
...installed.agents !== void 0 ? { agents: rootAgents } : {}
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
898
|
function writeLiteConfig(installConfig, targetPath) {
|
|
597
899
|
const configPath = targetPath ?? getLiteConfig();
|
|
598
900
|
try {
|
|
@@ -601,8 +903,10 @@ function writeLiteConfig(installConfig, targetPath) {
|
|
|
601
903
|
if (!installConfig.reset && existsSync3(configPath)) {
|
|
602
904
|
const { config: installed, error } = parseConfigFile(configPath);
|
|
603
905
|
if (error) throw new Error(`Failed to parse lite config: ${error}`);
|
|
604
|
-
if (installed) config =
|
|
906
|
+
if (installed) config = { ...config, ...configObject(installed) };
|
|
605
907
|
}
|
|
908
|
+
config = withoutLegacyPresets(config);
|
|
909
|
+
syncOpenCodeProfile(installConfig.reset);
|
|
606
910
|
const tmpPath = `${configPath}.tmp`;
|
|
607
911
|
const bakPath = `${configPath}.bak`;
|
|
608
912
|
const content = `${JSON.stringify(config, null, 2)}
|
|
@@ -635,9 +939,7 @@ function detectCurrentConfig() {
|
|
|
635
939
|
const { config: liteConfig } = parseConfig(getLiteConfig());
|
|
636
940
|
if (liteConfig && typeof liteConfig === "object") {
|
|
637
941
|
const configObj = liteConfig;
|
|
638
|
-
const
|
|
639
|
-
const presets = configObj.presets;
|
|
640
|
-
const agents = presets?.[presetName];
|
|
942
|
+
const agents = effectiveOpenCodeProfile(configObj).agents;
|
|
641
943
|
if (agents) {
|
|
642
944
|
const models = Object.values(agents).map((a) => a?.model).filter(Boolean);
|
|
643
945
|
result.hasOpenAI = models.some((m) => m?.startsWith("openai/"));
|
|
@@ -648,17 +950,31 @@ function detectCurrentConfig() {
|
|
|
648
950
|
|
|
649
951
|
// src/config/loader.ts
|
|
650
952
|
var PROMPTS_DIR_NAME = "thoth-agents";
|
|
651
|
-
function
|
|
953
|
+
function withoutInjectedDefaults(parsed, raw) {
|
|
954
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed) || raw === null || typeof raw !== "object") {
|
|
955
|
+
return parsed;
|
|
956
|
+
}
|
|
957
|
+
return Object.fromEntries(
|
|
958
|
+
Object.entries(parsed).filter(([key]) => Object.hasOwn(raw, key)).map(([key, value]) => [
|
|
959
|
+
key,
|
|
960
|
+
withoutInjectedDefaults(value, raw[key])
|
|
961
|
+
])
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
function loadConfigFromPath(configPath, unsupportedKeyPaths) {
|
|
652
965
|
try {
|
|
653
|
-
const content =
|
|
966
|
+
const content = fs2.readFileSync(configPath, "utf-8");
|
|
654
967
|
const rawConfig = JSON.parse(stripJsonComments(content));
|
|
968
|
+
if (rawConfig !== null && typeof rawConfig === "object" && ("preset" in rawConfig || "presets" in rawConfig)) {
|
|
969
|
+
unsupportedKeyPaths.add(configPath);
|
|
970
|
+
}
|
|
655
971
|
const result = PluginConfigSchema.safeParse(rawConfig);
|
|
656
972
|
if (!result.success) {
|
|
657
973
|
console.warn(`[thoth-agents] Invalid config at ${configPath}:`);
|
|
658
974
|
console.warn(result.error.format());
|
|
659
975
|
return null;
|
|
660
976
|
}
|
|
661
|
-
return result.data;
|
|
977
|
+
return withoutInjectedDefaults(result.data, rawConfig);
|
|
662
978
|
} catch (error) {
|
|
663
979
|
if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
|
|
664
980
|
console.warn(
|
|
@@ -672,10 +988,10 @@ function loadConfigFromPath(configPath) {
|
|
|
672
988
|
function findConfigPath(basePath) {
|
|
673
989
|
const jsoncPath = `${basePath}.jsonc`;
|
|
674
990
|
const jsonPath = `${basePath}.json`;
|
|
675
|
-
if (
|
|
991
|
+
if (fs2.existsSync(jsoncPath)) {
|
|
676
992
|
return jsoncPath;
|
|
677
993
|
}
|
|
678
|
-
if (
|
|
994
|
+
if (fs2.existsSync(jsonPath)) {
|
|
679
995
|
return jsonPath;
|
|
680
996
|
}
|
|
681
997
|
return null;
|
|
@@ -683,9 +999,9 @@ function findConfigPath(basePath) {
|
|
|
683
999
|
function deepMerge(base, override) {
|
|
684
1000
|
if (!base) return override;
|
|
685
1001
|
if (!override) return base;
|
|
686
|
-
const result =
|
|
1002
|
+
const result = Object.assign(/* @__PURE__ */ Object.create(null), base);
|
|
687
1003
|
for (const key of Object.keys(override)) {
|
|
688
|
-
const baseVal = base[key];
|
|
1004
|
+
const baseVal = Object.hasOwn(base, key) ? base[key] : void 0;
|
|
689
1005
|
const overrideVal = override[key];
|
|
690
1006
|
if (typeof baseVal === "object" && baseVal !== null && typeof overrideVal === "object" && overrideVal !== null && !Array.isArray(baseVal) && !Array.isArray(overrideVal)) {
|
|
691
1007
|
result[key] = deepMerge(
|
|
@@ -699,16 +1015,17 @@ function deepMerge(base, override) {
|
|
|
699
1015
|
return result;
|
|
700
1016
|
}
|
|
701
1017
|
function loadPluginConfig(directory) {
|
|
702
|
-
const userConfigBasePath =
|
|
703
|
-
const projectConfigBasePath =
|
|
1018
|
+
const userConfigBasePath = path2.join(getConfigDir(), "thoth-agents");
|
|
1019
|
+
const projectConfigBasePath = path2.join(
|
|
704
1020
|
directory,
|
|
705
1021
|
".opencode",
|
|
706
1022
|
"thoth-agents"
|
|
707
1023
|
);
|
|
708
1024
|
const userConfigPath = findConfigPath(userConfigBasePath);
|
|
709
1025
|
const projectConfigPath = findConfigPath(projectConfigBasePath);
|
|
710
|
-
|
|
711
|
-
|
|
1026
|
+
const unsupportedKeyPaths = /* @__PURE__ */ new Set();
|
|
1027
|
+
let config = userConfigPath ? loadConfigFromPath(userConfigPath, unsupportedKeyPaths) ?? {} : {};
|
|
1028
|
+
const projectConfig = projectConfigPath ? loadConfigFromPath(projectConfigPath, unsupportedKeyPaths) : null;
|
|
712
1029
|
if (projectConfig) {
|
|
713
1030
|
config = {
|
|
714
1031
|
...config,
|
|
@@ -719,51 +1036,40 @@ function loadPluginConfig(directory) {
|
|
|
719
1036
|
claudeCode: deepMerge(config.claudeCode, projectConfig.claudeCode)
|
|
720
1037
|
};
|
|
721
1038
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
1039
|
+
if (unsupportedKeyPaths.size > 0) {
|
|
1040
|
+
console.warn(
|
|
1041
|
+
`[thoth-agents] Config keys preset/presets are unsupported and ignored (${[...unsupportedKeyPaths].join(", ")}).`
|
|
1042
|
+
);
|
|
725
1043
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
`[thoth-agents] Preset "${config.preset}" not found (from ${presetSource}). Available presets: ${availablePresets}`
|
|
735
|
-
);
|
|
736
|
-
}
|
|
1044
|
+
const result = PluginConfigSchema.safeParse(config);
|
|
1045
|
+
if (!result.success) {
|
|
1046
|
+
const sourcePaths = [userConfigPath, projectConfigPath].filter(Boolean);
|
|
1047
|
+
console.warn(
|
|
1048
|
+
`[thoth-agents] Invalid merged config from ${sourcePaths.join(", ")}:`
|
|
1049
|
+
);
|
|
1050
|
+
console.warn(result.error.format());
|
|
1051
|
+
return {};
|
|
737
1052
|
}
|
|
738
|
-
return
|
|
1053
|
+
return result.data;
|
|
739
1054
|
}
|
|
740
|
-
function loadAgentPrompt(agentName
|
|
741
|
-
const
|
|
742
|
-
const promptsDir = path.join(getConfigDir(), PROMPTS_DIR_NAME);
|
|
743
|
-
const promptSearchDirs = presetDirName ? [path.join(promptsDir, presetDirName), promptsDir] : [promptsDir];
|
|
1055
|
+
function loadAgentPrompt(agentName) {
|
|
1056
|
+
const promptsDir = path2.join(getConfigDir(), PROMPTS_DIR_NAME);
|
|
744
1057
|
const result = {};
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
error instanceof Error ? error.message : String(error)
|
|
757
|
-
);
|
|
758
|
-
}
|
|
1058
|
+
const readPrompt = (fileName, errorPrefix) => {
|
|
1059
|
+
const promptPath = path2.join(promptsDir, fileName);
|
|
1060
|
+
if (!fs2.existsSync(promptPath)) return void 0;
|
|
1061
|
+
try {
|
|
1062
|
+
return fs2.readFileSync(promptPath, "utf-8");
|
|
1063
|
+
} catch (error) {
|
|
1064
|
+
console.warn(
|
|
1065
|
+
`[thoth-agents] ${errorPrefix} ${promptPath}:`,
|
|
1066
|
+
error instanceof Error ? error.message : String(error)
|
|
1067
|
+
);
|
|
1068
|
+
return void 0;
|
|
759
1069
|
}
|
|
760
|
-
return void 0;
|
|
761
1070
|
};
|
|
762
|
-
result.prompt =
|
|
763
|
-
|
|
764
|
-
"Error reading prompt file"
|
|
765
|
-
);
|
|
766
|
-
result.appendPrompt = readFirstPrompt(
|
|
1071
|
+
result.prompt = readPrompt(`${agentName}.md`, "Error reading prompt file");
|
|
1072
|
+
result.appendPrompt = readPrompt(
|
|
767
1073
|
`${agentName}_append.md`,
|
|
768
1074
|
"Error reading append prompt file"
|
|
769
1075
|
);
|
|
@@ -774,7 +1080,7 @@ function loadAgentPrompt(agentName, preset) {
|
|
|
774
1080
|
var text = { type: "string" };
|
|
775
1081
|
var texts = { type: "array", items: text };
|
|
776
1082
|
var nullableText = { type: ["string", "null"] };
|
|
777
|
-
function
|
|
1083
|
+
function object2(properties) {
|
|
778
1084
|
return {
|
|
779
1085
|
type: "object",
|
|
780
1086
|
properties,
|
|
@@ -785,7 +1091,7 @@ function object(properties) {
|
|
|
785
1091
|
function array(items) {
|
|
786
1092
|
return { type: "array", items };
|
|
787
1093
|
}
|
|
788
|
-
var common =
|
|
1094
|
+
var common = object2({
|
|
789
1095
|
schemaVersion: { type: "number", const: 1 },
|
|
790
1096
|
role: {
|
|
791
1097
|
type: "string",
|
|
@@ -797,7 +1103,7 @@ var common = object({
|
|
|
797
1103
|
},
|
|
798
1104
|
conclusion: text,
|
|
799
1105
|
evidence: array(
|
|
800
|
-
|
|
1106
|
+
object2({
|
|
801
1107
|
id: { type: "string", minLength: 1 },
|
|
802
1108
|
kind: text,
|
|
803
1109
|
locator: text,
|
|
@@ -807,7 +1113,7 @@ var common = object({
|
|
|
807
1113
|
})
|
|
808
1114
|
),
|
|
809
1115
|
verification: array(
|
|
810
|
-
|
|
1116
|
+
object2({
|
|
811
1117
|
check: text,
|
|
812
1118
|
method: text,
|
|
813
1119
|
outcome: { type: "string", enum: ["pass", "fail", "not-run"] },
|
|
@@ -816,7 +1122,7 @@ var common = object({
|
|
|
816
1122
|
})
|
|
817
1123
|
),
|
|
818
1124
|
risks: array(
|
|
819
|
-
|
|
1125
|
+
object2({
|
|
820
1126
|
description: text,
|
|
821
1127
|
severity: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
822
1128
|
blocking: { type: "boolean" },
|
|
@@ -824,11 +1130,11 @@ var common = object({
|
|
|
824
1130
|
})
|
|
825
1131
|
),
|
|
826
1132
|
openQuestions: array(
|
|
827
|
-
|
|
1133
|
+
object2({ question: text, owner: text, options: texts, facts: texts })
|
|
828
1134
|
)
|
|
829
1135
|
});
|
|
830
1136
|
var review = {
|
|
831
|
-
...
|
|
1137
|
+
...object2({
|
|
832
1138
|
kind: {
|
|
833
1139
|
type: "string",
|
|
834
1140
|
enum: ["plan-review", "final-verification"]
|
|
@@ -844,7 +1150,7 @@ var SPECIALIST_RESULT_CONTRACT = {
|
|
|
844
1150
|
fence: "thoth-result",
|
|
845
1151
|
common,
|
|
846
1152
|
roles: {
|
|
847
|
-
oracle:
|
|
1153
|
+
oracle: object2({ nextAction: nullableText, review })
|
|
848
1154
|
},
|
|
849
1155
|
reviewKinds: {
|
|
850
1156
|
"plan-review": { verdicts: ["OKAY", "REJECT"] },
|
|
@@ -2411,6 +2717,7 @@ function composeAgentPrompt({
|
|
|
2411
2717
|
}
|
|
2412
2718
|
|
|
2413
2719
|
export {
|
|
2720
|
+
PROFILE_ROLES,
|
|
2414
2721
|
SUBAGENT_NAMES,
|
|
2415
2722
|
ALL_AGENT_NAMES,
|
|
2416
2723
|
LEGACY_OPENAI_PRESET,
|
|
@@ -2419,15 +2726,26 @@ export {
|
|
|
2419
2726
|
DEFAULT_MODELS,
|
|
2420
2727
|
getDefaultOpenCodeModel,
|
|
2421
2728
|
getDefaultOpenCodeVariant,
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2729
|
+
resolveRoleProfile,
|
|
2730
|
+
mergeProfileModelChain,
|
|
2731
|
+
formatModelString,
|
|
2732
|
+
validateProfileName,
|
|
2733
|
+
ProfileConflictError,
|
|
2734
|
+
ModelProfileStore,
|
|
2425
2735
|
getConfigDir,
|
|
2426
2736
|
getOpenCodeManagedModelStatePath,
|
|
2427
2737
|
getExistingLiteConfigPath,
|
|
2428
2738
|
getExistingConfigPath,
|
|
2429
2739
|
ensureConfigDir,
|
|
2740
|
+
MODEL_MAPPINGS,
|
|
2430
2741
|
generateLiteConfig,
|
|
2742
|
+
openCodeProfileStore,
|
|
2743
|
+
activeOpenCodeProfile,
|
|
2744
|
+
effectiveOpenCodeProfile,
|
|
2745
|
+
withoutLegacyPresets,
|
|
2746
|
+
findPackageRoot,
|
|
2747
|
+
isValidPackageVersion,
|
|
2748
|
+
resolveExecutingPackageVersion,
|
|
2431
2749
|
parseConfig,
|
|
2432
2750
|
writeConfig,
|
|
2433
2751
|
pluginEntries,
|