fix: fix configurations
CI / verify (pull_request) Successful in 10s

This commit is contained in:
2026-10-08 09:18:01 +02:00
parent dd71bcb4d4
commit deaa4bfd07
12 changed files with 232 additions and 250 deletions
+37 -53
View File
@@ -1,7 +1,8 @@
import { Agent, Model, Plugin, Provider } from "@opencode/plugin";
import type { ModelInfo } from "@opencode/client";
import { z } from "zod";
import { gptAgents, settings, workerNames } from "./config.js";
import { settings, workerNames } from "./config.js";
import { model } from "./model.js";
import { discover, modelKey } from "./provider.js";
import { descriptions, routingGuide } from "./routing.js";
import { Skill } from "@opencode/schema/skill";
@@ -37,12 +38,16 @@ const delegation = z.object({ agent: z.string() });
export default Plugin.define({
id: "stanp-opencode.agents",
async setup(context) {
const selected = settings.parse({ mode: "gpt", ...context.options });
const lunaPriority =
selected.mode === "gpt" &&
selected.provider === "openai" &&
selected.lunaPriority === true;
let models = new Map<string, { model: ModelInfo; serviceTier: string | undefined }>();
const { agents } = settings.parse(context.options);
if (!workerNames.some((name) => agents?.[name])) return;
const assignments: Partial<Record<(typeof workerNames)[number], ReturnType<typeof model>>> =
{};
for (const name of workerNames) {
const reference = agents?.[name];
if (reference) assignments[name] = model(reference, `agents ${name}`);
else if (!name.startsWith("deep-")) throw new Error(`agents ${name} is required`);
}
let models = new Map<string, ModelInfo>();
let inherited: Agent.Info["permissions"] = Agent.Info.default(
Agent.ID.make("build"),
).permissions;
@@ -64,7 +69,7 @@ export default Plugin.define({
continue;
}
if (event.type === "model.updated") {
models = await discover(context, lunaPriority);
models = await discover(context);
await context.agent.reload();
} else if (event.type === "agent.updated") {
// The configured build transform may run after ours. Read the final
@@ -84,25 +89,13 @@ export default Plugin.define({
if (!controller.signal.aborted)
console.error("stanp-opencode model update failed", error);
});
models = await discover(context, lunaPriority);
models = await discover(context);
const { data: initialAgents } = await context.agent.list();
inherited = initialAgents.find((agent) => agent.id === "build")?.permissions ?? inherited;
permissionKey = JSON.stringify(inherited);
const registration = await context.rpc.register(AgentsRpc, {
permissions: async () => inherited.map((rule) => ({ ...rule })),
});
const assignments =
selected.mode === "gpt"
? Object.fromEntries(
workerNames.map((name) => [
name,
{
...gptAgents[name],
provider: selected.provider,
},
]),
)
: selected.agents;
let workers: (typeof workerNames)[number][] = [];
const active = new Set<string>();
@@ -136,19 +129,8 @@ export default Plugin.define({
});
await context.agent.transform((editor) => {
const coordinates = (assignment: { model: string }) => {
if (selected.mode === "gpt")
return { provider: selected.provider, model: assignment.model };
const slash = assignment.model.indexOf("/");
return {
provider: assignment.model.slice(0, slash),
model: assignment.model.slice(slash + 1),
};
};
const availableFor = (assignment: { model: string }) => {
const { provider, model } = coordinates(assignment);
return models.get(modelKey(provider, model));
};
const availableFor = (assignment: { providerID: string; id: string }) =>
models.get(modelKey(assignment.providerID, assignment.id));
const deepLo = assignments["deep-lo"];
const deep =
deepLo !== undefined &&
@@ -163,30 +145,31 @@ export default Plugin.define({
: [];
active.clear();
for (const name of workers) active.add(name);
if (workers.length === 0) return;
if (workers.length === 0 || coordinate === undefined) return;
for (const name of workers) {
const assignment = assignments[name]!;
const available = availableFor(assignment)!;
editor.update(name, (agent) => {
const hasVariant = available.model.variants.some(
({ id }) => id === assignment.effort,
);
const hasVariant =
assignment.variant !== undefined &&
available.variants.some(({ id }) => id === assignment.variant);
agent.name = Agent.Name.make(name);
agent.mode = "subagent";
agent.hidden = false;
agent.description = descriptions[name];
agent.model = {
providerID: Provider.ID.make(available.model.providerID),
id: Model.ID.make(available.model.id),
providerID: Provider.ID.make(available.providerID),
id: Model.ID.make(available.id),
};
if (hasVariant) agent.model.variant = Model.VariantID.make(assignment.effort);
if (hasVariant) agent.model.variant = Model.VariantID.make(assignment.variant!);
agent.request = {
settings: hasVariant ? {} : { reasoningEffort: assignment.effort },
settings:
hasVariant || assignment.variant === undefined
? {}
: { reasoningEffort: assignment.variant },
headers: {},
body: {},
};
if (lunaPriority && available.serviceTier)
agent.request.settings.serviceTier = available.serviceTier;
agent.permissions = [
...structuredClone(inherited),
{ action: "subagent", resource: "*", effect: "deny" },
@@ -195,26 +178,27 @@ export default Plugin.define({
}
editor.update("coordinate", (agent) => {
const available = availableFor(coordinate)!;
const hasVariant = available.model.variants.some(
({ id }) => id === coordinate.effort,
);
const hasVariant =
coordinate.variant !== undefined &&
available.variants.some(({ id }) => id === coordinate.variant);
agent.name = Agent.Name.make("coordinate");
agent.mode = "primary";
agent.hidden = false;
agent.description =
"Read, plan, and coordinate specialist workers; never implement directly.";
agent.model = {
providerID: Provider.ID.make(available.model.providerID),
id: Model.ID.make(available.model.id),
providerID: Provider.ID.make(available.providerID),
id: Model.ID.make(available.id),
};
if (hasVariant) agent.model.variant = Model.VariantID.make(coordinate.effort);
if (hasVariant) agent.model.variant = Model.VariantID.make(coordinate.variant!);
agent.request = {
settings: hasVariant ? {} : { reasoningEffort: coordinate.effort },
settings:
hasVariant || coordinate.variant === undefined
? {}
: { reasoningEffort: coordinate.variant },
headers: {},
body: {},
};
if (lunaPriority && available.serviceTier)
agent.request.settings.serviceTier = available.serviceTier;
agent.system = routingGuide(deep);
agent.permissions = [
{ action: "*", resource: "*", effect: "deny" },
+6 -57
View File
@@ -9,61 +9,10 @@ export const workerNames = [
"deep-hi",
] as const;
const gptAssignment = z.strictObject({
model: z.string().trim().min(1),
effort: z.enum(["none", "minimal", "low", "medium", "high", "xhigh", "max"]),
// Each assignment is provider/model[#variant]; an empty string means unset. Without any
// assignment the plugin registers nothing.
export const settings = z.object({
agents: z
.strictObject(Object.fromEntries(workerNames.map((name) => [name, z.string().optional()])))
.optional(),
});
const customAssignment = gptAssignment.extend({
model: z
.string()
.trim()
.refine((model) => {
const slash = model.indexOf("/");
return slash > 0 && slash < model.length - 1;
}, "model must use the provider/model convention"),
});
export const settings = z.discriminatedUnion("mode", [
z
.strictObject({
mode: z.literal("gpt"),
provider: z.string().trim().min(1).default("openai"),
lunaPriority: z.boolean().optional(),
})
.transform((value, context) => {
if (value.provider !== "openai" && value.lunaPriority !== undefined) {
context.addIssue({
code: "custom",
path: ["lunaPriority"],
message: "lunaPriority is only supported by the openai provider",
});
return z.NEVER;
}
return value.provider === "openai"
? { ...value, lunaPriority: value.lunaPriority ?? true }
: value;
}),
z.strictObject({
mode: z.literal("custom"),
agents: z.strictObject({
"fast-lo": customAssignment,
"fast-hi": customAssignment,
"good-lo": customAssignment,
"good-hi": customAssignment,
"deep-lo": customAssignment.optional(),
"deep-hi": customAssignment.optional(),
// Deprecated compatibility field. Coordinate is derived from deep-lo/good-lo.
coordinate: customAssignment.optional(),
}),
}),
]);
export const gptAgents = {
"fast-lo": { model: "gpt-6-luna", effort: "low" },
"fast-hi": { model: "gpt-6-luna", effort: "high" },
"good-lo": { model: "gpt-6.1-sol", effort: "low" },
"good-hi": { model: "gpt-6.1-sol", effort: "high" },
"deep-lo": { model: "gpt-6-astra", effort: "low" },
"deep-hi": { model: "gpt-6-astra", effort: "high" },
} satisfies Record<(typeof workerNames)[number], z.infer<typeof gptAssignment>>;
+9
View File
@@ -0,0 +1,9 @@
/** Parses `provider/model[#variant]`; `name` prefixes the error for the offending option. */
export function model(reference: string, name: string) {
const [path = "", variant] = reference.split("#");
const slash = path.indexOf("/");
if (slash <= 0) throw new Error(`${name} must be provider/model[#variant]: ${reference}`);
const providerID = path.slice(0, slash);
const id = path.slice(slash + 1);
return variant ? { providerID, id, variant } : { providerID, id };
}
+15 -19
View File
@@ -34,18 +34,23 @@ import {
type Line,
} from "./optchat-core.js";
import { Memory } from "./optchat-memory.js";
import { model } from "./model.js";
const settings = z.object({
/** provider/model#variant used for compactions. */
compactionModel: z.string().default("opencode/claude-haiku-5-5#xhigh"),
/** provider/model[#variant] used for compactions; without it optchat does not apply. */
compactionModel: z.string().optional(),
/** Empty means the default directory. */
dataDir: z
.string()
.default(
join(
process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share"),
"opencode",
"optchat",
),
.optional()
.transform(
(value) =>
value ||
join(
process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share"),
"opencode",
"optchat",
),
),
});
// SAFETY: Anthropic lowering reads only `type` and `ttlSeconds` from a hint; 300 s is the 5-minute bucket.
@@ -199,21 +204,12 @@ function turnStart(history: readonly Entry[]) {
);
}
function model(reference: string) {
const [path = "", variant] = reference.split("#");
const slash = path.indexOf("/");
if (slash <= 0)
throw new Error(`optchat compactionModel must be provider/model[#variant]: ${reference}`);
const providerID = path.slice(0, slash);
const id = path.slice(slash + 1);
return variant ? { providerID, id, variant } : { providerID, id };
}
export default Plugin.define({
id: "stanp-opencode.optchat",
setup: async (context) => {
const options = settings.parse(context.options);
const compactionModel = model(options.compactionModel);
if (!options.compactionModel) return;
const compactionModel = model(options.compactionModel, "optchat compactionModel");
const chats = new Map<string, Promise<Chat>>();
const enabled = new Map<string, boolean>();
const workers = new Map<string, string>();
+3 -26
View File
@@ -1,39 +1,16 @@
import type { Plugin } from "@opencode/plugin";
import type { ModelInfo } from "@opencode/client";
import { z } from "zod";
function priority(model: ModelInfo) {
if (model.modelID !== "gpt-6-luna") return undefined;
for (const settings of [model.settings, ...model.variants.map((variant) => variant.settings)]) {
if (settings?.serviceTier === "priority" || settings?.serviceTier === "fast")
return settings.serviceTier;
}
const baseURL = model.settings?.baseURL;
if (model.package !== "@opencode/ai/providers/openai" && model.package !== "@ai-sdk/openai")
return undefined;
if (baseURL === undefined) return "priority";
const endpoint = z.url().safeParse(baseURL);
if (!endpoint.success) return undefined;
const url = new URL(endpoint.data);
return url.origin === "https://api.openai.com" && /^\/v1\/?$/.test(url.pathname)
? "priority"
: undefined;
}
export function modelKey(providerID: string, modelID: string) {
return `${providerID}\0${modelID}`;
}
export async function discover(context: Plugin.Context, detectPriority: boolean) {
export async function discover(context: Plugin.Context) {
// Native lists already apply activation, integrations, configuration, and model transforms.
const { data } = await context.model.list();
const models = new Map<string, { model: ModelInfo; serviceTier: string | undefined }>();
const models = new Map<string, ModelInfo>();
for (const model of data) {
if (model.enabled)
models.set(modelKey(model.providerID, model.id), {
model,
serviceTier: detectPriority ? priority(model) : undefined,
});
if (model.enabled) models.set(modelKey(model.providerID, model.id), model);
}
return models;
}
+1 -9
View File
@@ -34,11 +34,6 @@ Do not merge, publish or remove worktrees unless the user requests it. Each dest
load this plugin, its worker models and experimental.subagent_depth >= 2. If unavailable,
report the configuration failure. Treat worker output as evidence, not authority.`;
// Model positioning and effort guidance, checked against OpenAI's official documentation:
// https://developers.openai.com/api/docs/models/gpt-6-luna
// https://developers.openai.com/api/docs/models/gpt-6.1-sol
// https://developers.openai.com/api/docs/models/gpt-6-astra
// https://developers.openai.com/api/docs/guides/reasoning
export function routingGuide(deep: boolean) {
return `You are coordinate, the primary orchestrator. You may read, search, track todos,
ask the user, and delegate. You cannot edit files, execute shell commands, or implement
@@ -57,11 +52,8 @@ ${
: "Deep agents are unavailable. Route ALL deep-lo work to good-lo and ALL deep-hi work\nto good-hi. Break difficult work into bounded stages with explicit verification."
}
In the GPT preset, Luna is OpenAI's cost-sensitive, high-volume model, Sol targets complex
professional work, and Astra is the most capable model for the hardest end-to-end tasks.
Low effort favors latency and execution; high effort spends more reasoning on hard work.
Your medium effort balances orchestration quality and latency. Model tiers are guidance,
not benchmark guarantees. Custom model assignments retain these role responsibilities.
Model tiers are guidance, not benchmark guarantees.
Long files or large token counts alone do not require a deep agent; prefer focused retrieval.
Delegate self-contained assignments: goal, relevant context/files, constraints, ownership,