opencode-magi 0.0.0-dev-20260524220537 → 0.0.0-dev-20260524221727
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 +14 -0
- package/dist/config/resolve.js +10 -0
- package/dist/config/validate.js +99 -40
- package/dist/index.js +6 -1
- package/package.json +1 -1
- package/schema.json +29 -10
package/README.md
CHANGED
|
@@ -155,6 +155,20 @@ Add the following content to the configuration file.
|
|
|
155
155
|
|
|
156
156
|
Entries with `ref` are expanded from `agents.refs`. Fields set alongside `ref` override fields from the preset.
|
|
157
157
|
|
|
158
|
+
`model` can be a single `provider/model` string or an ordered candidate array. Candidate arrays are resolved during validation against OpenCode's model catalog; the first available model is selected. Put provider-specific options on object candidates, not on the agent role.
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"model": [
|
|
163
|
+
"anthropic/claude-sonnet-4-5",
|
|
164
|
+
{
|
|
165
|
+
"id": "openai/gpt-5.1",
|
|
166
|
+
"options": { "reasoningEffort": "high" }
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
158
172
|
After refs are expanded, `review.agents[].account` is the GitHub account used to post reviews and approvals. Must be authenticated with `gh auth token --user <account>` and must be unique. `merge.editor.account` is used by `/magi:merge` to push fixes, close PRs, and merge PRs.
|
|
159
173
|
|
|
160
174
|
#### Validate config
|
package/dist/config/resolve.js
CHANGED
|
@@ -33,6 +33,12 @@ export function triageAgentKey(agent, index) {
|
|
|
33
33
|
export function validateReviewerId(id) {
|
|
34
34
|
return ID_PATTERN.test(id);
|
|
35
35
|
}
|
|
36
|
+
function normalizedModel(model) {
|
|
37
|
+
if (typeof model !== "string") {
|
|
38
|
+
throw new Error("model must be normalized before resolving agents");
|
|
39
|
+
}
|
|
40
|
+
return model;
|
|
41
|
+
}
|
|
36
42
|
function clonePermissionValue(value) {
|
|
37
43
|
return typeof value === "string" ? value : { ...value };
|
|
38
44
|
}
|
|
@@ -86,6 +92,7 @@ export function resolveAgents(config) {
|
|
|
86
92
|
editor: editor
|
|
87
93
|
? {
|
|
88
94
|
...editor,
|
|
95
|
+
model: normalizedModel(editor.model),
|
|
89
96
|
permission: resolveEditorPermission(agents, editor),
|
|
90
97
|
}
|
|
91
98
|
: undefined,
|
|
@@ -93,18 +100,21 @@ export function resolveAgents(config) {
|
|
|
93
100
|
...reviewer,
|
|
94
101
|
key: reviewerKey(reviewer, index),
|
|
95
102
|
index,
|
|
103
|
+
model: normalizedModel(reviewer.model),
|
|
96
104
|
permission: resolveReviewerPermission(agents, reviewer),
|
|
97
105
|
})),
|
|
98
106
|
triage: (config.triage?.agents ?? []).map((agent, index) => ({
|
|
99
107
|
...agent,
|
|
100
108
|
key: triageAgentKey(agent, index),
|
|
101
109
|
index,
|
|
110
|
+
model: normalizedModel(agent.model),
|
|
102
111
|
permission: resolveTriageAgentPermission(agents, agent),
|
|
103
112
|
})),
|
|
104
113
|
triageCreator: creator
|
|
105
114
|
? {
|
|
106
115
|
...creator,
|
|
107
116
|
account: creator.account ?? "",
|
|
117
|
+
model: normalizedModel(creator.model),
|
|
108
118
|
permission: resolveTriageCreatorPermission(agents, creator),
|
|
109
119
|
}
|
|
110
120
|
: undefined,
|
package/dist/config/validate.js
CHANGED
|
@@ -4,7 +4,7 @@ import { access } from "node:fs/promises";
|
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { isAbsolute, join } from "node:path";
|
|
6
6
|
import schema from "../../schema.json" with { type: "json" };
|
|
7
|
-
import { resolveAgents, validateReviewerId } from "./resolve";
|
|
7
|
+
import { resolveAgents, reviewerKey, triageAgentKey, validateReviewerId, } from "./resolve";
|
|
8
8
|
const RESERVED_REVIEWER_KEYS = new Set(["editor", "orchestrator", "system"]);
|
|
9
9
|
const PERMISSION_ACTIONS = new Set(["allow", "ask", "deny"]);
|
|
10
10
|
const AJV = new Ajv2020({ allErrors: true, strict: false });
|
|
@@ -27,7 +27,6 @@ const REVIEWER_KEYS = new Set([
|
|
|
27
27
|
"account",
|
|
28
28
|
"id",
|
|
29
29
|
"model",
|
|
30
|
-
"options",
|
|
31
30
|
"permissions",
|
|
32
31
|
"persona",
|
|
33
32
|
]);
|
|
@@ -35,7 +34,6 @@ const EDITOR_KEYS = new Set([
|
|
|
35
34
|
"account",
|
|
36
35
|
"author",
|
|
37
36
|
"model",
|
|
38
|
-
"options",
|
|
39
37
|
"permissions",
|
|
40
38
|
"persona",
|
|
41
39
|
]);
|
|
@@ -43,7 +41,6 @@ const TRIAGE_AGENT_KEYS = new Set([
|
|
|
43
41
|
"account",
|
|
44
42
|
"id",
|
|
45
43
|
"model",
|
|
46
|
-
"options",
|
|
47
44
|
"permissions",
|
|
48
45
|
"persona",
|
|
49
46
|
]);
|
|
@@ -51,7 +48,6 @@ const TRIAGE_CREATOR_KEYS = new Set([
|
|
|
51
48
|
"account",
|
|
52
49
|
"author",
|
|
53
50
|
"model",
|
|
54
|
-
"options",
|
|
55
51
|
"permissions",
|
|
56
52
|
"persona",
|
|
57
53
|
]);
|
|
@@ -145,6 +141,7 @@ const TRIAGE_PROMPT_KEYS = new Set([
|
|
|
145
141
|
"existingPr",
|
|
146
142
|
"reconsider",
|
|
147
143
|
]);
|
|
144
|
+
const MODEL_CANDIDATE_KEYS = new Set(["id", "options"]);
|
|
148
145
|
function githubHost(config) {
|
|
149
146
|
return config.github?.host ?? "github.com";
|
|
150
147
|
}
|
|
@@ -275,26 +272,85 @@ function validatePermissionConfig(permission, path, errors) {
|
|
|
275
272
|
}
|
|
276
273
|
}
|
|
277
274
|
}
|
|
278
|
-
function
|
|
279
|
-
if (!model)
|
|
280
|
-
return;
|
|
275
|
+
function modelValidationError(model, path, catalog) {
|
|
281
276
|
const slash = model.indexOf("/");
|
|
282
|
-
if (slash <= 0 || slash === model.length - 1)
|
|
283
|
-
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
277
|
+
if (slash <= 0 || slash === model.length - 1)
|
|
278
|
+
return `${path} must be a full OpenCode model ID in provider/model form`;
|
|
286
279
|
if (!catalog)
|
|
287
|
-
return;
|
|
280
|
+
return undefined;
|
|
288
281
|
const providerId = model.slice(0, slash);
|
|
289
282
|
const modelId = model.slice(slash + 1);
|
|
290
283
|
const models = catalog[providerId];
|
|
291
|
-
if (!models)
|
|
292
|
-
|
|
284
|
+
if (!models)
|
|
285
|
+
return `${path} uses unknown OpenCode provider: ${providerId}`;
|
|
286
|
+
if (!models.includes(modelId))
|
|
287
|
+
return `${path} uses unknown OpenCode model: ${model}`;
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
function validateModelId(model, path, errors, catalog) {
|
|
291
|
+
const error = modelValidationError(model, path, catalog);
|
|
292
|
+
if (error) {
|
|
293
|
+
errors.push(error);
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
function readModelCandidate(value, path, errors) {
|
|
299
|
+
if (typeof value === "string")
|
|
300
|
+
return { id: value };
|
|
301
|
+
if (!isPlainObject(value)) {
|
|
302
|
+
errors.push(`${path} must be a string or an object`);
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
validateKnownKeys(value, path, MODEL_CANDIDATE_KEYS, errors);
|
|
306
|
+
if (typeof value.id !== "string") {
|
|
307
|
+
errors.push(`${path}.id must be a string`);
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
if (value.options != null && !isPlainObject(value.options)) {
|
|
311
|
+
errors.push(`${path}.options must be an object`);
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
return { id: value.id, options: value.options };
|
|
315
|
+
}
|
|
316
|
+
function validateAndNormalizeModel(target, path, errors, catalog) {
|
|
317
|
+
const model = target.model;
|
|
318
|
+
if (typeof model === "string") {
|
|
319
|
+
validateModelId(model, path, errors, catalog);
|
|
293
320
|
return;
|
|
294
321
|
}
|
|
295
|
-
if (!
|
|
296
|
-
|
|
322
|
+
if (!Array.isArray(model)) {
|
|
323
|
+
if (model != null)
|
|
324
|
+
errors.push(`${path} must be a string or an array`);
|
|
325
|
+
return;
|
|
297
326
|
}
|
|
327
|
+
if (!model.length) {
|
|
328
|
+
errors.push(`${path} must contain at least one model candidate`);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (!catalog) {
|
|
332
|
+
errors.push(`${path} requires an OpenCode model catalog`);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const candidateErrors = [];
|
|
336
|
+
for (const [index, value] of model.entries()) {
|
|
337
|
+
const candidatePath = `${path}[${index}]`;
|
|
338
|
+
const candidate = readModelCandidate(value, candidatePath, errors);
|
|
339
|
+
if (!candidate)
|
|
340
|
+
continue;
|
|
341
|
+
const idPath = isPlainObject(value) ? `${candidatePath}.id` : candidatePath;
|
|
342
|
+
const error = modelValidationError(candidate.id, idPath, catalog);
|
|
343
|
+
if (!error) {
|
|
344
|
+
target.model = candidate.id;
|
|
345
|
+
if (candidate.options)
|
|
346
|
+
target.options = candidate.options;
|
|
347
|
+
else
|
|
348
|
+
delete target.options;
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
candidateErrors.push(error);
|
|
352
|
+
}
|
|
353
|
+
errors.push(`${path} must contain at least one usable OpenCode model candidate${candidateErrors.length ? ` (${candidateErrors.join("; ")})` : ""}`);
|
|
298
354
|
}
|
|
299
355
|
function validateReviewerList(reviewers, path, errors, catalog) {
|
|
300
356
|
if (reviewers == null)
|
|
@@ -315,14 +371,11 @@ function validateReviewerList(reviewers, path, errors, catalog) {
|
|
|
315
371
|
validateKnownKeys(reviewer, `${path}[${index}]`, REVIEWER_KEYS, errors);
|
|
316
372
|
if (!reviewer.model)
|
|
317
373
|
errors.push(`${path}[${index}].model is required`);
|
|
318
|
-
|
|
319
|
-
validateModel(reviewer.model, `${path}[${index}].model`, errors, catalog);
|
|
374
|
+
validateAndNormalizeModel(reviewer, `${path}[${index}].model`, errors, catalog);
|
|
320
375
|
if (!reviewer.account)
|
|
321
376
|
errors.push(`${path}[${index}].account is required`);
|
|
322
377
|
validateString(reviewer.account, `${path}[${index}].account`, errors);
|
|
323
378
|
validateString(reviewer.persona, `${path}[${index}].persona`, errors);
|
|
324
|
-
if (reviewer.options != null && !isPlainObject(reviewer.options))
|
|
325
|
-
errors.push(`${path}[${index}].options must be an object`);
|
|
326
379
|
validatePermissionConfig(reviewer.permissions, `${path}[${index}].permissions`, errors);
|
|
327
380
|
if (reviewer.id) {
|
|
328
381
|
if (!validateReviewerId(reviewer.id)) {
|
|
@@ -353,14 +406,11 @@ function validateTriageAgentList(agents, path, errors, catalog) {
|
|
|
353
406
|
validateKnownKeys(agent, `${path}[${index}]`, TRIAGE_AGENT_KEYS, errors);
|
|
354
407
|
if (!agent.model)
|
|
355
408
|
errors.push(`${path}[${index}].model is required`);
|
|
356
|
-
|
|
357
|
-
validateModel(agent.model, `${path}[${index}].model`, errors, catalog);
|
|
409
|
+
validateAndNormalizeModel(agent, `${path}[${index}].model`, errors, catalog);
|
|
358
410
|
if (!agent.account)
|
|
359
411
|
errors.push(`${path}[${index}].account is required`);
|
|
360
412
|
validateString(agent.account, `${path}[${index}].account`, errors);
|
|
361
413
|
validateString(agent.persona, `${path}[${index}].persona`, errors);
|
|
362
|
-
if (agent.options != null && !isPlainObject(agent.options))
|
|
363
|
-
errors.push(`${path}[${index}].options must be an object`);
|
|
364
414
|
validatePermissionConfig(agent.permissions, `${path}[${index}].permissions`, errors);
|
|
365
415
|
if (agent.id) {
|
|
366
416
|
if (!validateReviewerId(agent.id)) {
|
|
@@ -406,15 +456,11 @@ function validateEditor(editor, path, errors, catalog) {
|
|
|
406
456
|
if (!editor.model)
|
|
407
457
|
errors.push(`${path}.model is required`);
|
|
408
458
|
validateKnownKeys(editor, path, EDITOR_KEYS, errors);
|
|
409
|
-
|
|
459
|
+
validateAndNormalizeModel(editor, `${path}.model`, errors, catalog);
|
|
410
460
|
validateString(editor.account, `${path}.account`, errors);
|
|
411
461
|
validateString(editor.persona, `${path}.persona`, errors);
|
|
412
|
-
validateModel(editor.model, `${path}.model`, errors, catalog);
|
|
413
462
|
if (!editor.account)
|
|
414
463
|
errors.push(`${path}.account is required`);
|
|
415
|
-
if (editor.options != null && !isPlainObject(editor.options)) {
|
|
416
|
-
errors.push(`${path}.options must be an object`);
|
|
417
|
-
}
|
|
418
464
|
validatePermissionConfig(editor.permissions, `${path}.permissions`, errors);
|
|
419
465
|
const author = editor.author;
|
|
420
466
|
if (!author || !isPlainObject(author)) {
|
|
@@ -450,12 +496,8 @@ function validateTriageCreator(creator, path, errors, catalog) {
|
|
|
450
496
|
if (!creator.model)
|
|
451
497
|
errors.push(`${path}.model is required`);
|
|
452
498
|
validateString(creator.account, `${path}.account`, errors);
|
|
453
|
-
|
|
499
|
+
validateAndNormalizeModel(creator, `${path}.model`, errors, catalog);
|
|
454
500
|
validateString(creator.persona, `${path}.persona`, errors);
|
|
455
|
-
validateModel(creator.model, `${path}.model`, errors, catalog);
|
|
456
|
-
if (creator.options != null && !isPlainObject(creator.options)) {
|
|
457
|
-
errors.push(`${path}.options must be an object`);
|
|
458
|
-
}
|
|
459
501
|
validatePermissionConfig(creator.permissions, `${path}.permissions`, errors);
|
|
460
502
|
const author = creator.author;
|
|
461
503
|
if (!author || !isPlainObject(author)) {
|
|
@@ -684,7 +726,12 @@ function validateTriage(config, errors, options) {
|
|
|
684
726
|
errors.push("triage.agents is required");
|
|
685
727
|
validateTriageAgentList(triage.agents, "triage.agents", errors, options.modelCatalog);
|
|
686
728
|
if (Array.isArray(triage.agents)) {
|
|
687
|
-
const resolvedTriageAgents =
|
|
729
|
+
const resolvedTriageAgents = triage.agents.map((agent, index) => ({
|
|
730
|
+
account: agent && typeof agent === "object" && typeof agent.account === "string"
|
|
731
|
+
? agent.account
|
|
732
|
+
: "",
|
|
733
|
+
key: agent && typeof agent === "object" ? triageAgentKey(agent, index) : "",
|
|
734
|
+
}));
|
|
688
735
|
validateResolvedTriageAgents(resolvedTriageAgents, "triage.resolvedAgents", errors);
|
|
689
736
|
if (reporter != null &&
|
|
690
737
|
!resolvedTriageAgents.some((agent) => agent.key === reporter)) {
|
|
@@ -778,10 +825,10 @@ async function fetchPermissions(config, exec, account) {
|
|
|
778
825
|
return JSON.parse(raw);
|
|
779
826
|
}
|
|
780
827
|
async function validateWorktreeConfig(config, exec, options, errors) {
|
|
781
|
-
const
|
|
782
|
-
|
|
828
|
+
const checkEditor = Boolean(config.merge?.editor &&
|
|
829
|
+
(options.requireEditor || options.requireWorktreeConfig));
|
|
783
830
|
const checkTriageCreator = Boolean(config.triage?.automation?.create &&
|
|
784
|
-
|
|
831
|
+
config.triage?.creator &&
|
|
785
832
|
(options.requireTriage || options.requireWorktreeConfig));
|
|
786
833
|
if (!checkEditor && !checkTriageCreator)
|
|
787
834
|
return;
|
|
@@ -853,6 +900,9 @@ export async function validateConfig(config, options = {}) {
|
|
|
853
900
|
const warnings = [];
|
|
854
901
|
if (!config || typeof config !== "object")
|
|
855
902
|
errors.push("config must be an object");
|
|
903
|
+
if (options.requireModelCatalog && !options.modelCatalog) {
|
|
904
|
+
errors.push("OpenCode model catalog could not be loaded");
|
|
905
|
+
}
|
|
856
906
|
expandAgentRefs(config, errors);
|
|
857
907
|
if (config && typeof config === "object")
|
|
858
908
|
validateJsonSchema(config, errors);
|
|
@@ -880,7 +930,16 @@ export async function validateConfig(config, options = {}) {
|
|
|
880
930
|
errors.push("review.agents is required");
|
|
881
931
|
validateReviewerList(config.review.agents, "review.agents", errors, options.modelCatalog);
|
|
882
932
|
if (Array.isArray(config.review.agents)) {
|
|
883
|
-
validateResolvedReviewers(
|
|
933
|
+
validateResolvedReviewers(config.review.agents.map((reviewer, index) => ({
|
|
934
|
+
account: reviewer &&
|
|
935
|
+
typeof reviewer === "object" &&
|
|
936
|
+
typeof reviewer.account === "string"
|
|
937
|
+
? reviewer.account
|
|
938
|
+
: "",
|
|
939
|
+
key: reviewer && typeof reviewer === "object"
|
|
940
|
+
? reviewerKey(reviewer, index)
|
|
941
|
+
: "",
|
|
942
|
+
})), "review.resolvedAgents", errors);
|
|
884
943
|
}
|
|
885
944
|
}
|
|
886
945
|
if (options.requireTriage && !config.triage) {
|
package/dist/index.js
CHANGED
|
@@ -379,6 +379,7 @@ export async function validateMagiConfigFiles(directory, options = {}) {
|
|
|
379
379
|
: undefined,
|
|
380
380
|
modelCatalog: options.modelCatalog,
|
|
381
381
|
requireGithub: hasProjectConfig && Boolean(mergedConfig.review?.agents),
|
|
382
|
+
requireModelCatalog: true,
|
|
382
383
|
requireWorktreeConfig: true,
|
|
383
384
|
});
|
|
384
385
|
loadedFrom = existing.map((status) => status.path).join(", ");
|
|
@@ -430,7 +431,8 @@ export const MagiPlugin = async ({ client, directory }) => {
|
|
|
430
431
|
.then(extractModelCatalog)
|
|
431
432
|
.catch(() => catalogClient.provider
|
|
432
433
|
?.list({ query: { directory } })
|
|
433
|
-
.then(extractModelCatalog))
|
|
434
|
+
.then(extractModelCatalog))
|
|
435
|
+
.catch(() => undefined);
|
|
434
436
|
return modelCatalogPromise;
|
|
435
437
|
}
|
|
436
438
|
return {
|
|
@@ -475,6 +477,7 @@ export const MagiPlugin = async ({ client, directory }) => {
|
|
|
475
477
|
exec: retryingExec,
|
|
476
478
|
modelCatalog: await modelCatalog(),
|
|
477
479
|
requireEditor: true,
|
|
480
|
+
requireModelCatalog: true,
|
|
478
481
|
});
|
|
479
482
|
if (!validation.ok)
|
|
480
483
|
return JSON.stringify(validation, null, 2);
|
|
@@ -517,6 +520,7 @@ export const MagiPlugin = async ({ client, directory }) => {
|
|
|
517
520
|
directory,
|
|
518
521
|
exec: retryingExec,
|
|
519
522
|
modelCatalog: await modelCatalog(),
|
|
523
|
+
requireModelCatalog: true,
|
|
520
524
|
});
|
|
521
525
|
if (!validation.ok)
|
|
522
526
|
return JSON.stringify(validation, null, 2);
|
|
@@ -557,6 +561,7 @@ export const MagiPlugin = async ({ client, directory }) => {
|
|
|
557
561
|
exec: retryingExec,
|
|
558
562
|
modelCatalog: await modelCatalog(),
|
|
559
563
|
requireEditor: config.triage?.automation?.merge === true,
|
|
564
|
+
requireModelCatalog: true,
|
|
560
565
|
requireReview: config.triage?.automation?.review === true ||
|
|
561
566
|
config.triage?.automation?.merge === true,
|
|
562
567
|
requireTriage: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-magi",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-20260524221727",
|
|
4
4
|
"description": "Multi-agent PR review and merge orchestration plugin for OpenCode.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Hirotomo Yamada <hirotomo.yamada@avap.co.jp>",
|
package/schema.json
CHANGED
|
@@ -60,8 +60,7 @@
|
|
|
60
60
|
"additionalProperties": false,
|
|
61
61
|
"properties": {
|
|
62
62
|
"id": { "type": "string", "pattern": "^[A-Za-z0-9_-]+$" },
|
|
63
|
-
"model": { "
|
|
64
|
-
"options": { "type": "object", "additionalProperties": true },
|
|
63
|
+
"model": { "$ref": "#/$defs/modelConfig" },
|
|
65
64
|
"account": { "type": "string", "minLength": 1 },
|
|
66
65
|
"author": {
|
|
67
66
|
"type": "object",
|
|
@@ -83,8 +82,7 @@
|
|
|
83
82
|
"properties": {
|
|
84
83
|
"ref": { "type": "string", "minLength": 1 },
|
|
85
84
|
"id": { "type": "string", "pattern": "^[A-Za-z0-9_-]+$" },
|
|
86
|
-
"model": { "
|
|
87
|
-
"options": { "type": "object", "additionalProperties": true },
|
|
85
|
+
"model": { "$ref": "#/$defs/modelConfig" },
|
|
88
86
|
"account": { "type": "string", "minLength": 1 },
|
|
89
87
|
"permissions": { "$ref": "#/$defs/permissions" },
|
|
90
88
|
"persona": { "type": "string" }
|
|
@@ -97,8 +95,7 @@
|
|
|
97
95
|
"additionalProperties": false,
|
|
98
96
|
"properties": {
|
|
99
97
|
"ref": { "type": "string", "minLength": 1 },
|
|
100
|
-
"model": { "
|
|
101
|
-
"options": { "type": "object", "additionalProperties": true },
|
|
98
|
+
"model": { "$ref": "#/$defs/modelConfig" },
|
|
102
99
|
"account": { "type": "string", "minLength": 1 },
|
|
103
100
|
"author": {
|
|
104
101
|
"type": "object",
|
|
@@ -121,9 +118,8 @@
|
|
|
121
118
|
"properties": {
|
|
122
119
|
"ref": { "type": "string", "minLength": 1 },
|
|
123
120
|
"id": { "type": "string", "pattern": "^[A-Za-z0-9_-]+$" },
|
|
124
|
-
"model": { "
|
|
121
|
+
"model": { "$ref": "#/$defs/modelConfig" },
|
|
125
122
|
"account": { "type": "string", "minLength": 1 },
|
|
126
|
-
"options": { "type": "object", "additionalProperties": true },
|
|
127
123
|
"permissions": { "$ref": "#/$defs/permissions" },
|
|
128
124
|
"persona": { "type": "string" }
|
|
129
125
|
}
|
|
@@ -136,8 +132,7 @@
|
|
|
136
132
|
"properties": {
|
|
137
133
|
"ref": { "type": "string", "minLength": 1 },
|
|
138
134
|
"account": { "type": "string", "minLength": 1 },
|
|
139
|
-
"model": { "
|
|
140
|
-
"options": { "type": "object", "additionalProperties": true },
|
|
135
|
+
"model": { "$ref": "#/$defs/modelConfig" },
|
|
141
136
|
"author": {
|
|
142
137
|
"type": "object",
|
|
143
138
|
"required": ["name", "email"],
|
|
@@ -151,6 +146,30 @@
|
|
|
151
146
|
"persona": { "type": "string" }
|
|
152
147
|
}
|
|
153
148
|
},
|
|
149
|
+
"modelConfig": {
|
|
150
|
+
"oneOf": [
|
|
151
|
+
{ "type": "string", "minLength": 1 },
|
|
152
|
+
{
|
|
153
|
+
"type": "array",
|
|
154
|
+
"minItems": 1,
|
|
155
|
+
"items": {
|
|
156
|
+
"oneOf": [
|
|
157
|
+
{ "type": "string", "minLength": 1 },
|
|
158
|
+
{ "$ref": "#/$defs/modelCandidate" }
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
"modelCandidate": {
|
|
165
|
+
"type": "object",
|
|
166
|
+
"required": ["id"],
|
|
167
|
+
"additionalProperties": false,
|
|
168
|
+
"properties": {
|
|
169
|
+
"id": { "type": "string", "minLength": 1 },
|
|
170
|
+
"options": { "type": "object", "additionalProperties": true }
|
|
171
|
+
}
|
|
172
|
+
},
|
|
154
173
|
"automation": {
|
|
155
174
|
"type": "object",
|
|
156
175
|
"additionalProperties": false,
|