mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-10 07:08:19 +03:00
feat(core): support disable-model-invocation in skill frontmatter (#52747)
This commit is contained in:
@@ -10,6 +10,7 @@ const Frontmatter = Schema.Struct({
|
||||
name: Schema.String.pipe(Schema.optional),
|
||||
description: Schema.String.pipe(Schema.optional),
|
||||
metadata: Schema.Unknown.pipe(Schema.optional),
|
||||
"disable-model-invocation": Schema.Unknown.pipe(Schema.optional),
|
||||
})
|
||||
const decodeFrontmatter = SchemaParser.decodeUnknownResult(Frontmatter)
|
||||
|
||||
@@ -22,12 +23,16 @@ const metadataBoolean = (metadata: unknown, key: string) => {
|
||||
if (metadata === undefined || metadata === null || typeof metadata !== "object" || Array.isArray(metadata)) {
|
||||
return undefined
|
||||
}
|
||||
const value = (metadata as Record<string, unknown>)[key]
|
||||
return booleanValue((metadata as Record<string, unknown>)[key])
|
||||
}
|
||||
|
||||
const booleanValue = (value: unknown) => {
|
||||
if (typeof value === "boolean") return value
|
||||
if (typeof value === "number") return value === 1 ? true : value === 0 ? false : undefined
|
||||
if (typeof value !== "string") return undefined
|
||||
const normalized = value.trim().toLowerCase()
|
||||
if (normalized === "true") return true
|
||||
if (normalized === "false") return false
|
||||
if (["true", "yes", "on", "1"].includes(normalized)) return true
|
||||
if (["false", "no", "off", "0"].includes(normalized)) return false
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -41,7 +46,9 @@ export function parse(directory: string, filepath: string, content: string): Par
|
||||
path.dirname(filepath) === directory && path.basename(filepath) !== "SKILL.md"
|
||||
? path.basename(filepath, ".md")
|
||||
: path.basename(path.dirname(filepath))
|
||||
const autoinvoke = metadataBoolean(frontmatter.metadata, "opencode/autoinvoke")
|
||||
const opencodeAutoinvoke = metadataBoolean(frontmatter.metadata, "opencode/autoinvoke")
|
||||
const disableModelInvocation = booleanValue(frontmatter["disable-model-invocation"])
|
||||
const autoinvoke = opencodeAutoinvoke ?? (disableModelInvocation ? false : undefined)
|
||||
return {
|
||||
_tag: "Parsed",
|
||||
skill: {
|
||||
|
||||
@@ -123,6 +123,18 @@ function emitAndWait(update: Watcher.Update) {
|
||||
}
|
||||
|
||||
describe("SkillFile.parse", () => {
|
||||
test("reads disable-model-invocation as autoinvoke", () => {
|
||||
const parse = (frontmatter: string) =>
|
||||
SkillFile.parse("/repo/skills", "/repo/skills/deploy/SKILL.md", `---\n${frontmatter}\n---\n# deploy`)
|
||||
expect(parse("disable-model-invocation: true")).toMatchObject({ skill: { autoinvoke: false } })
|
||||
expect(parse("disable-model-invocation: yes")).toMatchObject({ skill: { autoinvoke: false } })
|
||||
expect(parse("disable-model-invocation: false")).not.toMatchObject({ skill: { autoinvoke: expect.anything() } })
|
||||
expect(parse("disable-model-invocation: maybe")).not.toMatchObject({ skill: { autoinvoke: expect.anything() } })
|
||||
expect(
|
||||
parse("disable-model-invocation: true\nmetadata:\n opencode/autoinvoke: true"),
|
||||
).toMatchObject({ skill: { autoinvoke: true } })
|
||||
})
|
||||
|
||||
test("parses root and nested skill ids and metadata flags", () => {
|
||||
const directory = "/repo/skills"
|
||||
expect(
|
||||
|
||||
@@ -135,10 +135,11 @@ Prepare the changelog, version bump, tag, and release notes.
|
||||
| `slash` | Set to `false` to hide the skill from interactive command catalogs |
|
||||
| `metadata.opencode/slash` | Boolean or `"true"`/`"false"`; overrides `slash` |
|
||||
| `metadata.opencode/autoinvoke` | Set to `false` to omit the skill from the model's available list |
|
||||
| `disable-model-invocation` | Set to `true` for the same effect as `opencode/autoinvoke: false` |
|
||||
|
||||
All frontmatter is optional at runtime. Add a clear `description` when the model should discover the skill; skills without one are not advertised.
|
||||
|
||||
`opencode/autoinvoke: false` only hides the skill from the model's available list. The skill remains registered and can still be loaded explicitly by ID. V2 accepts portability fields such as `license` and `compatibility` but does not interpret them.
|
||||
`opencode/autoinvoke: false` only hides the skill from the model's available list. The skill remains registered and can still be loaded explicitly by ID. When both `disable-model-invocation` and `opencode/autoinvoke` are set, `opencode/autoinvoke` wins. V2 accepts portability fields such as `license` and `compatibility` but does not interpret them.
|
||||
|
||||
## IDs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user