mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 10:14:34 +03:00
fix(core): preserve legacy markdown agent variants
This commit is contained in:
committed by
GitHub
Unverified
parent
4ea368e09e
commit
f1eed8bf11
@@ -184,6 +184,15 @@ function decode(file: { directory: string; filepath: string; primary: boolean },
|
||||
.replace(/\.md$/, "")
|
||||
const body = markdown.content.trim()
|
||||
const legacy = Object.keys(markdown.data).some((key) => !agentKeys.has(key))
|
||||
// Join legacy model + variant without sending native request/permissions through migration.
|
||||
// Embedded and structured native selections, and a variant without a model, stay unchanged.
|
||||
const data =
|
||||
typeof markdown.data.model === "string" &&
|
||||
!markdown.data.model.includes("#") &&
|
||||
typeof markdown.data.variant === "string" &&
|
||||
/^[^#]+$/.test(markdown.data.variant)
|
||||
? { ...markdown.data, model: `${markdown.data.model}#${markdown.data.variant}` }
|
||||
: markdown.data
|
||||
const agent = legacy
|
||||
? Option.getOrUndefined(
|
||||
Option.map(
|
||||
@@ -191,9 +200,7 @@ function decode(file: { directory: string; filepath: string; primary: boolean },
|
||||
ConfigMigrateV1.migrateAgent,
|
||||
),
|
||||
)
|
||||
: Option.getOrUndefined(
|
||||
decodeAgent({ ...markdown.data, system: body }, { errors: "all", propertyOrder: "original" }),
|
||||
)
|
||||
: Option.getOrUndefined(decodeAgent({ ...data, system: body }, { errors: "all", propertyOrder: "original" }))
|
||||
if (!agent) return
|
||||
const info = Option.getOrUndefined(
|
||||
decodeConfig({
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Agent } from "@opencode/core/agent"
|
||||
import { Bus } from "@opencode/core/bus"
|
||||
import { Config } from "@opencode/core/config"
|
||||
import { Directory, Document, Event, Info } from "@opencode/schema/config"
|
||||
import { Model } from "@opencode/schema/model"
|
||||
import { ConfigAgentPlugin } from "@opencode/core/config/plugin/agent"
|
||||
import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode/util/effect/layer-node"
|
||||
@@ -17,7 +18,7 @@ import { AbsolutePath } from "@opencode/core/schema"
|
||||
import { ConfigMigrateV1 } from "@opencode/core/v1/config/migrate"
|
||||
import { ConfigAgentV1 } from "@opencode/core/v1/config/agent"
|
||||
import { advance, drain } from "../lib/clock"
|
||||
import { tmpdir } from "../fixture/tmpdir"
|
||||
import { tmpdir, tmpdirScoped } from "../fixture/tmpdir"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { agentHost, host } from "../plugin/host"
|
||||
|
||||
@@ -60,6 +61,76 @@ test("keeps schema fields and name out of legacy agent options", () => {
|
||||
})
|
||||
|
||||
describe("ConfigAgentPlugin.Plugin", () => {
|
||||
for (const item of [
|
||||
{ name: "separate legacy variant", frontmatter: "model: example/chat\nvariant: high", model: "example/chat#high" },
|
||||
{ name: "unqualified model", frontmatter: "model: example/chat", model: "example/chat" },
|
||||
{ name: "embedded native variant", frontmatter: "model: example/chat#high", model: "example/chat#high" },
|
||||
{
|
||||
name: "structured native variant",
|
||||
frontmatter: "model:\n providerID: example\n model: chat\n variant: high",
|
||||
model: "example/chat#high",
|
||||
},
|
||||
{
|
||||
name: "structured unqualified model",
|
||||
frontmatter: "model:\n providerID: example\n model: chat",
|
||||
model: "example/chat",
|
||||
},
|
||||
{ name: "standalone variant", frontmatter: "variant: high", model: undefined },
|
||||
{
|
||||
name: "embedded native variant with an ignored separate variant",
|
||||
frontmatter: "model: example/chat#high\nvariant: low",
|
||||
model: "example/chat#high",
|
||||
},
|
||||
{
|
||||
name: "structured native variant with an ignored separate variant",
|
||||
frontmatter: "model:\n providerID: example\n model: chat\n variant: high\nvariant: low",
|
||||
model: "example/chat#high",
|
||||
},
|
||||
]) {
|
||||
for (const native of [false, true]) {
|
||||
it.live(`loads Markdown ${item.name}${native ? " with native request and permissions" : ""}`, () =>
|
||||
Effect.gen(function* () {
|
||||
const agent = yield* loadMarkdownAgent(
|
||||
native
|
||||
? `${item.frontmatter}
|
||||
request:
|
||||
headers:
|
||||
x-agent: native
|
||||
body:
|
||||
effort: high
|
||||
permissions:
|
||||
- action: edit
|
||||
resource: "*"
|
||||
effect: deny`
|
||||
: item.frontmatter,
|
||||
)
|
||||
expect(agent.model).toEqual(item.model === undefined ? undefined : Model.Ref.parse(item.model))
|
||||
expect(agent.request).toEqual({
|
||||
settings: {},
|
||||
headers: native ? { "x-agent": "native" } : {},
|
||||
body: native ? { effort: "high" } : {},
|
||||
})
|
||||
if (native) {
|
||||
expect(agent.permissions).toContainEqual({ action: "edit", resource: "*", effect: "deny" })
|
||||
expect(Permission.evaluate("edit", "example.txt", agent.permissions).effect).toBe("deny")
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for (const variant of [undefined, "high"]) {
|
||||
it.live(`loads Markdown legacy temperature ${variant ? "with" : "without"} a separate variant`, () =>
|
||||
Effect.gen(function* () {
|
||||
const agent = yield* loadMarkdownAgent(
|
||||
`model: example/chat\ntemperature: 0.5${variant ? `\nvariant: ${variant}` : ""}`,
|
||||
)
|
||||
expect(agent.model).toEqual(Model.Ref.parse(variant ? "example/chat#high" : "example/chat"))
|
||||
expect(agent.request).toEqual({ settings: {}, headers: {}, body: { temperature: 0.5 } })
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
it.effect("matches POSIX paths against home-relative permissions", () =>
|
||||
Effect.gen(function* () {
|
||||
const permissions = yield* loadHomePermissions("/home/test")
|
||||
@@ -560,6 +631,26 @@ Use native v2 fields.`,
|
||||
)
|
||||
})
|
||||
|
||||
function loadMarkdownAgent(frontmatter: string) {
|
||||
return Effect.gen(function* () {
|
||||
const tmp = yield* tmpdirScoped()
|
||||
const fs = yield* FSUtil.Service
|
||||
yield* fs.makeDirectory(path.join(tmp.path, "agents"))
|
||||
yield* fs.writeFileString(
|
||||
path.join(tmp.path, "agents", "reviewer.md"),
|
||||
`---\n${frontmatter}\n---\nReview carefully.`,
|
||||
)
|
||||
const agents = yield* Agent.Service
|
||||
yield* ConfigAgentPlugin.Plugin.effect(host({ agent: agentHost(agents) })).pipe(
|
||||
Effect.provide(Config.testLayer([directoryEntry(tmp.path)])),
|
||||
)
|
||||
const agent = yield* agents.get(Agent.ID.make("reviewer"))
|
||||
if (!agent) throw new Error("expected configured Markdown agent")
|
||||
expect(agent.system).toBe("Review carefully.")
|
||||
return agent
|
||||
})
|
||||
}
|
||||
|
||||
function directoryEntry(directory: string) {
|
||||
return new Directory({ type: "directory", path: AbsolutePath.make(directory) })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user